Repository: 1Panel-dev/MaxKB Branch: v2 Commit: 641a3a88275c Files: 1905 Total size: 8.4 MB Directory structure: gitextract_dhtxbcma/ ├── .dockerignore ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ └── feature.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── build-and-push-python-pg.yml │ ├── build-and-push-vector-model.yml │ ├── build-and-push.yml │ ├── create-pr-from-push.yml │ ├── issue-translator.yml │ ├── llm-code-review.yml │ ├── sync2gitee.yml │ └── typos_check.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_CN.md ├── SECURITY.md ├── USE-CASES.md ├── apps/ │ ├── __init__.py │ ├── application/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── application_access_token.py │ │ │ ├── application_api.py │ │ │ ├── application_api_key.py │ │ │ ├── application_chat.py │ │ │ ├── application_chat_link.py │ │ │ ├── application_chat_record.py │ │ │ ├── application_stats.py │ │ │ └── application_version.py │ │ ├── apps.py │ │ ├── chat_pipeline/ │ │ │ ├── I_base_chat_pipeline.py │ │ │ ├── __init__.py │ │ │ ├── pipeline_manage.py │ │ │ └── step/ │ │ │ ├── __init__.py │ │ │ ├── chat_step/ │ │ │ │ ├── __init__.py │ │ │ │ ├── i_chat_step.py │ │ │ │ └── impl/ │ │ │ │ └── base_chat_step.py │ │ │ ├── generate_human_message_step/ │ │ │ │ ├── __init__.py │ │ │ │ ├── i_generate_human_message_step.py │ │ │ │ └── impl/ │ │ │ │ └── base_generate_human_message_step.py │ │ │ ├── reset_problem_step/ │ │ │ │ ├── __init__.py │ │ │ │ ├── i_reset_problem_step.py │ │ │ │ └── impl/ │ │ │ │ └── base_reset_problem_step.py │ │ │ └── search_dataset_step/ │ │ │ ├── __init__.py │ │ │ ├── i_search_dataset_step.py │ │ │ └── impl/ │ │ │ └── base_search_dataset_step.py │ │ ├── flow/ │ │ │ ├── __init__.py │ │ │ ├── backend/ │ │ │ │ ├── __init__.py │ │ │ │ └── sandbox_shell.py │ │ │ ├── common.py │ │ │ ├── compare/ │ │ │ │ ├── __init__.py │ │ │ │ ├── compare.py │ │ │ │ ├── contain_compare.py │ │ │ │ ├── end_with.py │ │ │ │ ├── equal_compare.py │ │ │ │ ├── ge_compare.py │ │ │ │ ├── gt_compare.py │ │ │ │ ├── is_not_null_compare.py │ │ │ │ ├── is_not_true.py │ │ │ │ ├── is_null_compare.py │ │ │ │ ├── is_true.py │ │ │ │ ├── le_compare.py │ │ │ │ ├── len_equal_compare.py │ │ │ │ ├── len_ge_compare.py │ │ │ │ ├── len_gt_compare.py │ │ │ │ ├── len_le_compare.py │ │ │ │ ├── len_lt_compare.py │ │ │ │ ├── lt_compare.py │ │ │ │ ├── not_contain_compare.py │ │ │ │ ├── not_equal_compare.py │ │ │ │ └── start_with.py │ │ │ ├── default_workflow.json │ │ │ ├── default_workflow_en.json │ │ │ ├── default_workflow_zh.json │ │ │ ├── default_workflow_zh_Hant.json │ │ │ ├── i_step_node.py │ │ │ ├── knowledge_loop_workflow_manage.py │ │ │ ├── knowledge_workflow_manage.py │ │ │ ├── loop_workflow_manage.py │ │ │ ├── step_node/ │ │ │ │ ├── __init__.py │ │ │ │ ├── ai_chat_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_chat_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_chat_node.py │ │ │ │ ├── application_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_application_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_application_node.py │ │ │ │ ├── condition_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_condition_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_condition_node.py │ │ │ │ ├── data_source_local_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_data_source_local_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_data_source_local_node.py │ │ │ │ ├── data_source_web_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_data_source_web_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_data_source_web_node.py │ │ │ │ ├── direct_reply_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_reply_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_reply_node.py │ │ │ │ ├── document_extract_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_document_extract_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_document_extract_node.py │ │ │ │ ├── document_split_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_document_split_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_document_split_node.py │ │ │ │ ├── form_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_form_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_form_node.py │ │ │ │ ├── image_generate_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_image_generate_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_image_generate_node.py │ │ │ │ ├── image_to_video_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_image_to_video_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_image_to_video_node.py │ │ │ │ ├── image_understand_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_image_understand_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_image_understand_node.py │ │ │ │ ├── intent_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_intent_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── base_intent_node.py │ │ │ │ │ └── prompt_template.py │ │ │ │ ├── knowledge_write_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_knowledge_write_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_knowledge_write_node.py │ │ │ │ ├── loop_break_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_loop_break_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_loop_break_node.py │ │ │ │ ├── loop_continue_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_loop_continue_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_loop_continue_node.py │ │ │ │ ├── loop_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_loop_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_loop_node.py │ │ │ │ ├── loop_start_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_loop_start_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_start_node.py │ │ │ │ ├── mcp_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_mcp_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_mcp_node.py │ │ │ │ ├── parameter_extraction_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_parameter_extraction_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_parameter_extraction_node.py │ │ │ │ ├── question_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_question_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_question_node.py │ │ │ │ ├── reranker_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_reranker_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_reranker_node.py │ │ │ │ ├── search_document_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_search_document_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_search_document_node.py │ │ │ │ ├── search_knowledge_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_search_knowledge_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_search_knowledge_node.py │ │ │ │ ├── speech_to_text_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_speech_to_text_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_speech_to_text_node.py │ │ │ │ ├── start_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_start_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_start_node.py │ │ │ │ ├── text_to_speech_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_text_to_speech_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_text_to_speech_node.py │ │ │ │ ├── text_to_video_step_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_text_to_video_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_text_to_video_node.py │ │ │ │ ├── tool_lib_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_tool_lib_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_tool_lib_node.py │ │ │ │ ├── tool_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_tool_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_tool_node.py │ │ │ │ ├── tool_start_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_tool_start_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_tool_start_node.py │ │ │ │ ├── tool_workflow_lib_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_tool_workflow_lib_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_tool_workflow_lib_node.py │ │ │ │ ├── variable_aggregation_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_variable_aggregation_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_variable_aggregation_node.py │ │ │ │ ├── variable_assign_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_variable_assign_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_variable_assign_node.py │ │ │ │ ├── variable_splitting_node/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── i_variable_splitting_node.py │ │ │ │ │ └── impl/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── base_variable_splitting_node.py │ │ │ │ └── video_understand_step_node/ │ │ │ │ ├── __init__.py │ │ │ │ ├── i_video_understand_node.py │ │ │ │ └── impl/ │ │ │ │ ├── __init__.py │ │ │ │ └── base_video_understand_node.py │ │ │ ├── tool_loop_workflow_manage.py │ │ │ ├── tool_workflow_manage.py │ │ │ ├── tools.py │ │ │ └── workflow_manage.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_application_simple_mcp.py │ │ │ ├── 0003_application_stt_model_params_setting_and_more.py │ │ │ ├── 0004_application_application_enable_and_more.py │ │ │ ├── 0005_chatrecord_vote_other_content_chatrecord_vote_reason.py │ │ │ ├── 0006_application_file_clean_time.py │ │ │ ├── 0007_applicationapikey_expire_time_and_more.py │ │ │ ├── 0008_chat_ip_address_chat_source_chatrecord_ip_address_and_more.py │ │ │ ├── 0009_clean_application_knowledge_mapping.py │ │ │ ├── 0010_chatsharelink.py │ │ │ ├── 0011_application_skill_tool_ids.py │ │ │ ├── 0012_remove_applicationapikey_user.py │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── application.py │ │ │ ├── application_access_token.py │ │ │ ├── application_api_key.py │ │ │ └── application_chat.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── application.py │ │ │ ├── application_access_token.py │ │ │ ├── application_api_key.py │ │ │ ├── application_chat.py │ │ │ ├── application_chat_link.py │ │ │ ├── application_chat_record.py │ │ │ ├── application_folder.py │ │ │ ├── application_stats.py │ │ │ ├── application_version.py │ │ │ └── common.py │ │ ├── sql/ │ │ │ ├── chat_record_count_trend.sql │ │ │ ├── count_chat_record.sql │ │ │ ├── customer_count_trend.sql │ │ │ ├── export_application_chat.sql │ │ │ ├── export_application_chat_ee.sql │ │ │ ├── get_token_usage.sql │ │ │ ├── get_token_usage_ee.sql │ │ │ ├── list_application.sql │ │ │ ├── list_application_chat.sql │ │ │ ├── list_application_chat_ee.sql │ │ │ ├── list_application_user.sql │ │ │ ├── list_application_user_ee.sql │ │ │ ├── list_knowledge_paragraph_by_paragraph_id.sql │ │ │ ├── top_questions.sql │ │ │ └── top_questions_ee.sql │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── application.py │ │ ├── application_access_token.py │ │ ├── application_api_key.py │ │ ├── application_chat.py │ │ ├── application_chat_link.py │ │ ├── application_chat_record.py │ │ ├── application_stats.py │ │ └── application_version.py │ ├── chat/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── chat_api.py │ │ │ ├── chat_authentication_api.py │ │ │ ├── chat_embed_api.py │ │ │ └── vote_api.py │ │ ├── apps.py │ │ ├── mcp/ │ │ │ ├── __init__.py │ │ │ └── tools.py │ │ ├── migrations/ │ │ │ └── __init__.py │ │ ├── models/ │ │ │ └── __init__.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── chat.py │ │ │ ├── chat_authentication.py │ │ │ ├── chat_embed_serializers.py │ │ │ └── chat_record.py │ │ ├── template/ │ │ │ ├── embed.js │ │ │ └── generate_prompt_system │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── chat.py │ │ ├── chat_embed.py │ │ ├── chat_record.py │ │ └── mcp.py │ ├── common/ │ │ ├── __init__.py │ │ ├── auth/ │ │ │ ├── __init__.py │ │ │ ├── authenticate.py │ │ │ ├── authentication.py │ │ │ ├── common.py │ │ │ └── handle/ │ │ │ ├── __init__.py │ │ │ ├── auth_base_handle.py │ │ │ └── impl/ │ │ │ ├── __init__.py │ │ │ ├── application_key.py │ │ │ ├── chat_anonymous_user_token.py │ │ │ └── user_token.py │ │ ├── cache/ │ │ │ ├── __init__.py │ │ │ └── mem_cache.py │ │ ├── cache_data/ │ │ │ ├── __init__.py │ │ │ ├── application_access_token_cache.py │ │ │ └── application_api_key_cache.py │ │ ├── chunk/ │ │ │ ├── __init__.py │ │ │ ├── i_chunk_handle.py │ │ │ └── impl/ │ │ │ ├── __init__.py │ │ │ └── mark_chunk_handle.py │ │ ├── config/ │ │ │ ├── __init__.py │ │ │ ├── embedding_config.py │ │ │ └── tokenizer_manage_config.py │ │ ├── constants/ │ │ │ ├── __init__.py │ │ │ ├── authentication_type.py │ │ │ ├── cache_version.py │ │ │ ├── exception_code_constants.py │ │ │ └── permission_constants.py │ │ ├── database_model_manage/ │ │ │ ├── __init__.py │ │ │ ├── database_model_manage.py │ │ │ └── handle/ │ │ │ ├── __init__.py │ │ │ ├── base_handle.py │ │ │ └── impl/ │ │ │ ├── __init__.py │ │ │ └── default_base_model_handle.py │ │ ├── db/ │ │ │ ├── __init__.py │ │ │ ├── compiler.py │ │ │ ├── search.py │ │ │ └── sql_execute.py │ │ ├── encoder/ │ │ │ ├── __init__.py │ │ │ └── encoder.py │ │ ├── event/ │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ └── listener_manage.py │ │ ├── exception/ │ │ │ ├── __init__.py │ │ │ ├── app_exception.py │ │ │ └── handle_exception.py │ │ ├── field/ │ │ │ ├── __init__.py │ │ │ └── common.py │ │ ├── forms/ │ │ │ ├── __init__.py │ │ │ ├── array_object_card.py │ │ │ ├── base_field.py │ │ │ ├── base_form.py │ │ │ ├── label/ │ │ │ │ ├── __init__.py │ │ │ │ ├── base_label.py │ │ │ │ └── tooltip_label.py │ │ │ ├── multi_select.py │ │ │ ├── object_card.py │ │ │ ├── password_input.py │ │ │ ├── radio_button_field.py │ │ │ ├── radio_card_field.py │ │ │ ├── radio_field.py │ │ │ ├── single_select_field.py │ │ │ ├── slider_field.py │ │ │ ├── switch_field.py │ │ │ ├── tab_card.py │ │ │ ├── table_checkbox.py │ │ │ ├── table_radio.py │ │ │ └── text_input_field.py │ │ ├── handle/ │ │ │ ├── __init__.py │ │ │ ├── base_parse_qa_handle.py │ │ │ ├── base_parse_table_handle.py │ │ │ ├── base_split_handle.py │ │ │ ├── base_to_response.py │ │ │ ├── handle_exception.py │ │ │ └── impl/ │ │ │ ├── __init__.py │ │ │ ├── common_handle.py │ │ │ ├── qa/ │ │ │ │ ├── __init__.py │ │ │ │ ├── csv_parse_qa_handle.py │ │ │ │ ├── md_parse_qa_handle.py │ │ │ │ ├── xls_parse_qa_handle.py │ │ │ │ ├── xlsx_parse_qa_handle.py │ │ │ │ └── zip_parse_qa_handle.py │ │ │ ├── response/ │ │ │ │ ├── __init__.py │ │ │ │ ├── loop_to_response.py │ │ │ │ ├── openai_to_response.py │ │ │ │ └── system_to_response.py │ │ │ ├── table/ │ │ │ │ ├── __init__.py │ │ │ │ ├── csv_parse_table_handle.py │ │ │ │ ├── xls_parse_table_handle.py │ │ │ │ └── xlsx_parse_table_handle.py │ │ │ └── text/ │ │ │ ├── __init__.py │ │ │ ├── csv_split_handle.py │ │ │ ├── doc_split_handle.py │ │ │ ├── html_split_handle.py │ │ │ ├── pdf_split_handle.py │ │ │ ├── text_split_handle.py │ │ │ ├── xls_split_handle.py │ │ │ ├── xlsx_split_handle.py │ │ │ └── zip_split_handle.py │ │ ├── init/ │ │ │ ├── init_doc.py │ │ │ └── init_template.py │ │ ├── job/ │ │ │ ├── __init__.py │ │ │ ├── clean_chat_job.py │ │ │ ├── clean_debug_file_job.py │ │ │ ├── client_access_num_job.py │ │ │ └── scheduler.py │ │ ├── lock/ │ │ │ ├── __init__.py │ │ │ ├── base_lock.py │ │ │ └── impl/ │ │ │ └── __init__.py │ │ ├── log/ │ │ │ ├── __init__.py │ │ │ └── log.py │ │ ├── management/ │ │ │ ├── __init__.py │ │ │ └── commands/ │ │ │ ├── __init__.py │ │ │ ├── celery.py │ │ │ ├── restart.py │ │ │ ├── services/ │ │ │ │ ├── __init__.py │ │ │ │ ├── command.py │ │ │ │ ├── hands.py │ │ │ │ ├── services/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── base.py │ │ │ │ │ ├── celery_base.py │ │ │ │ │ ├── celery_default.py │ │ │ │ │ ├── gunicorn.py │ │ │ │ │ ├── local_model.py │ │ │ │ │ └── scheduler.py │ │ │ │ └── utils.py │ │ │ ├── start.py │ │ │ ├── status.py │ │ │ └── stop.py │ │ ├── middleware/ │ │ │ ├── __init__.py │ │ │ ├── chat_headers_middleware.py │ │ │ ├── cross_domain_middleware.py │ │ │ ├── doc_headers_middleware.py │ │ │ └── gzip.py │ │ ├── mixins/ │ │ │ ├── __init__.py │ │ │ ├── api_mixin.py │ │ │ └── app_model_mixin.py │ │ ├── result/ │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── result.py │ │ ├── sql/ │ │ │ └── list_embedding_text.sql │ │ ├── template/ │ │ │ ├── email_template_en.html │ │ │ ├── email_template_zh.html │ │ │ └── email_template_zh_Hant.html │ │ └── utils/ │ │ ├── __init__.py │ │ ├── cache_util.py │ │ ├── chat_link_code.py │ │ ├── common.py │ │ ├── fork.py │ │ ├── lock.py │ │ ├── logger.py │ │ ├── page_utils.py │ │ ├── rsa_util.py │ │ ├── shared_resource_auth.py │ │ ├── split_model.py │ │ ├── tool_code.py │ │ └── ts_vecto_util.py │ ├── folders/ │ │ ├── __init__.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ └── folder.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ └── folder.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ └── folder.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ └── folder.py │ ├── knowledge/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── document.py │ │ │ ├── file.py │ │ │ ├── knowledge.py │ │ │ ├── knowledge_version.py │ │ │ ├── knowledge_workflow.py │ │ │ ├── paragraph.py │ │ │ ├── problem.py │ │ │ └── tag.py │ │ ├── apps.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_file_source_type.py │ │ │ ├── 0003_tag_documenttag.py │ │ │ ├── 0004_alter_document_type_alter_knowledge_type_and_more.py │ │ │ ├── 0005_knowledgeaction.py │ │ │ ├── 0006_paragraph_chunks.py │ │ │ ├── 0007_remove_knowledgeworkflowversion_workflow_and_more.py │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── knowledge.py │ │ │ └── knowledge_action.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ ├── document.py │ │ │ ├── knowledge.py │ │ │ ├── knowledge_folder.py │ │ │ ├── knowledge_version.py │ │ │ ├── knowledge_workflow.py │ │ │ ├── paragraph.py │ │ │ ├── problem.py │ │ │ └── tag.py │ │ ├── sql/ │ │ │ ├── blend_search.sql │ │ │ ├── embedding_search.sql │ │ │ ├── hit_test.sql │ │ │ ├── keywords_search.sql │ │ │ ├── list_document.sql │ │ │ ├── list_knowledge.sql │ │ │ ├── list_knowledge_application.sql │ │ │ ├── list_knowledge_user.sql │ │ │ ├── list_knowledge_user_ee.sql │ │ │ ├── list_paragraph.sql │ │ │ ├── list_paragraph_document_name.sql │ │ │ ├── list_problem.sql │ │ │ ├── list_problem_mapping.sql │ │ │ ├── update_document_char_length.sql │ │ │ ├── update_document_status_meta.sql │ │ │ └── update_paragraph_status.sql │ │ ├── task/ │ │ │ ├── __init__.py │ │ │ ├── embedding.py │ │ │ ├── generate.py │ │ │ ├── handler.py │ │ │ └── sync.py │ │ ├── template/ │ │ │ ├── csv_template_en.csv │ │ │ ├── csv_template_zh.csv │ │ │ ├── csv_template_zh_Hant.csv │ │ │ ├── excel_template_en.xlsx │ │ │ ├── excel_template_zh.xlsx │ │ │ ├── excel_template_zh_Hant.xlsx │ │ │ ├── table_template_en.csv │ │ │ ├── table_template_en.xlsx │ │ │ ├── table_template_zh.csv │ │ │ ├── table_template_zh.xlsx │ │ │ ├── table_template_zh_Hant.csv │ │ │ └── table_template_zh_Hant.xlsx │ │ ├── tests.py │ │ ├── urls.py │ │ ├── vector/ │ │ │ ├── __init__.py │ │ │ ├── base_vector.py │ │ │ └── pg_vector.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── common.py │ │ ├── document.py │ │ ├── knowledge.py │ │ ├── knowledge_workflow.py │ │ ├── knowledge_workflow_version.py │ │ ├── paragraph.py │ │ ├── problem.py │ │ └── tag.py │ ├── local_model/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations/ │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── model_management.py │ │ │ ├── system_setting.py │ │ │ └── user.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── model_apply_serializers.py │ │ │ └── rsa_util.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ └── model_apply.py │ ├── locales/ │ │ ├── en_US/ │ │ │ └── LC_MESSAGES/ │ │ │ └── django.po │ │ ├── zh_CN/ │ │ │ └── LC_MESSAGES/ │ │ │ └── django.po │ │ └── zh_Hant/ │ │ └── LC_MESSAGES/ │ │ └── django.po │ ├── manage.py │ ├── maxkb/ │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── conf.py │ │ ├── const.py │ │ ├── settings/ │ │ │ ├── __init__.py │ │ │ ├── auth/ │ │ │ │ ├── __init__.py │ │ │ │ ├── model.py │ │ │ │ └── web.py │ │ │ ├── base/ │ │ │ │ ├── __init__.py │ │ │ │ ├── model.py │ │ │ │ └── web.py │ │ │ ├── lib.py │ │ │ ├── logging.py │ │ │ └── mem.py │ │ ├── urls/ │ │ │ ├── __init__.py │ │ │ ├── model.py │ │ │ └── web.py │ │ └── wsgi/ │ │ ├── __init__.py │ │ ├── model.py │ │ └── web.py │ ├── models_provider/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── model.py │ │ │ └── provide.py │ │ ├── apps.py │ │ ├── base_model_provider.py │ │ ├── base_ttv.py │ │ ├── constants/ │ │ │ ├── __init__.py │ │ │ └── model_provider_constants.py │ │ ├── impl/ │ │ │ ├── __init__.py │ │ │ ├── aliyun_bai_lian_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── aliyun_bai_lian_model_provider.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── itv.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── asr_stt.py │ │ │ │ │ │ ├── default_stt.py │ │ │ │ │ │ ├── omni_stt.py │ │ │ │ │ │ └── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ ├── tts.py │ │ │ │ │ └── ttv.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── embedding.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ ├── reranker.py │ │ │ │ ├── stt/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── asr_stt.py │ │ │ │ │ ├── default_stt.py │ │ │ │ │ ├── omni_stt.py │ │ │ │ │ └── stt.py │ │ │ │ ├── tti.py │ │ │ │ ├── tts.py │ │ │ │ └── ttv.py │ │ │ ├── anthropic_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── anthropic_model_provider.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── image.py │ │ │ │ │ └── llm.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── image.py │ │ │ │ └── llm.py │ │ │ ├── aws_bedrock_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── aws_bedrock_model_provider.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── reranker.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── embedding.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ └── reranker.py │ │ │ ├── azure_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── azure_model_provider.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── azure_chat_model.py │ │ │ │ ├── embedding.py │ │ │ │ ├── image.py │ │ │ │ ├── stt.py │ │ │ │ ├── tti.py │ │ │ │ └── tts.py │ │ │ ├── base_chat_open_ai.py │ │ │ ├── base_stt.py │ │ │ ├── base_tti.py │ │ │ ├── base_tts.py │ │ │ ├── deepseek_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── llm.py │ │ │ │ ├── deepseek_model_provider.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ └── llm.py │ │ │ ├── docker_ai_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ ├── docker_ai_model_provider.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── embedding.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ ├── reranker.py │ │ │ │ ├── stt.py │ │ │ │ ├── tti.py │ │ │ │ └── tts.py │ │ │ ├── gemini_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ └── tti.py │ │ │ │ ├── gemini_model_provider.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── embedding.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ ├── stt.py │ │ │ │ └── tti.py │ │ │ ├── kimi_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── llm.py │ │ │ │ ├── kimi_model_provider.py │ │ │ │ └── model/ │ │ │ │ ├── __init__.py │ │ │ │ └── llm.py │ │ │ ├── local_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── model.py │ │ │ │ │ │ └── web.py │ │ │ │ │ └── reranker/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── model.py │ │ │ │ │ └── web.py │ │ │ │ ├── local_model_provider.py │ │ │ │ └── model/ │ │ │ │ ├── embedding/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── model.py │ │ │ │ │ └── web.py │ │ │ │ └── reranker/ │ │ │ │ ├── __init__.py │ │ │ │ ├── model.py │ │ │ │ └── web.py │ │ │ ├── ollama_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── reranker.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── reranker.py │ │ │ │ └── ollama_model_provider.py │ │ │ ├── openai_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ └── openai_model_provider.py │ │ │ ├── qwen_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ └── __init__.py │ │ │ │ └── model/ │ │ │ │ └── __init__.py │ │ │ ├── regolo_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── tti.py │ │ │ │ ├── model/ │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── tti.py │ │ │ │ └── regolo_model_provider.py │ │ │ ├── siliconCloud_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ └── siliconCloud_model_provider.py │ │ │ ├── tencent_cloud_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── llm.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── llm.py │ │ │ │ └── tencent_cloud_model_provider.py │ │ │ ├── tencent_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── tti.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── hunyuan.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ └── tti.py │ │ │ │ └── tencent_model_provider.py │ │ │ ├── vllm_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ └── whisper_stt.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ └── whisper_sst.py │ │ │ │ └── vllm_model_provider.py │ │ │ ├── volcanic_engine_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── bigModel_stt.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ ├── tts.py │ │ │ │ │ └── ttv.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── bigModel_stt.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ ├── tts.py │ │ │ │ │ └── ttv.py │ │ │ │ └── volcanic_engine_model_provider.py │ │ │ ├── wenxin_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ └── llm.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ └── llm.py │ │ │ │ └── wenxin_model_provider.py │ │ │ ├── xf_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tts/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── default_tts.py │ │ │ │ │ │ ├── super_humanoid_tts.py │ │ │ │ │ │ └── tts.py │ │ │ │ │ └── zh_en_stt.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tts/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── default_tts.py │ │ │ │ │ │ ├── super_humanoid_tts.py │ │ │ │ │ │ └── tts.py │ │ │ │ │ └── zh_en_stt.py │ │ │ │ └── xf_model_provider.py │ │ │ ├── xinference_model_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── credential/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ ├── model/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── embedding.py │ │ │ │ │ ├── image.py │ │ │ │ │ ├── llm.py │ │ │ │ │ ├── reranker.py │ │ │ │ │ ├── stt.py │ │ │ │ │ ├── tti.py │ │ │ │ │ └── tts.py │ │ │ │ └── xinference_model_provider.py │ │ │ └── zhipu_model_provider/ │ │ │ ├── __init__.py │ │ │ ├── credential/ │ │ │ │ ├── __init__.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ └── tti.py │ │ │ ├── model/ │ │ │ │ ├── __init__.py │ │ │ │ ├── image.py │ │ │ │ ├── llm.py │ │ │ │ └── tti.py │ │ │ └── zhipu_model_provider.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ └── model_management.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── model_apply_serializers.py │ │ │ └── model_serializer.py │ │ ├── sql/ │ │ │ ├── list_model.sql │ │ │ ├── list_model_user.sql │ │ │ └── list_model_user_ee.sql │ │ ├── tests.py │ │ ├── tools.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── model.py │ │ ├── model_apply.py │ │ └── provide.py │ ├── ops/ │ │ ├── __init__.py │ │ └── celery/ │ │ ├── __init__.py │ │ ├── const.py │ │ ├── decorator.py │ │ ├── heartbeat.py │ │ ├── hmac_signed_serializer.py │ │ ├── logger.py │ │ ├── signal_handler.py │ │ └── utils.py │ ├── oss/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations/ │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── retrieval_urls.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ └── file.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── views/ │ │ │ ├── __init__.py │ │ │ └── file.py │ │ └── views.py │ ├── system_manage/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── email_setting.py │ │ │ ├── resource_mapping.py │ │ │ ├── system.py │ │ │ └── user_resource_permission.py │ │ ├── apps.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_refresh_collation_reindex.py │ │ │ ├── 0003_alter_workspaceuserresourcepermission_target.py │ │ │ ├── 0004_alter_systemsetting_type_and_more.py │ │ │ ├── 0005_resourcemapping.py │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── chat_user.py │ │ │ ├── log_management.py │ │ │ ├── resource_mapping.py │ │ │ ├── system_setting.py │ │ │ └── workspace_user_permission.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── email_setting.py │ │ │ ├── resource_mapping_serializers.py │ │ │ ├── system.py │ │ │ ├── user_resource_permission.py │ │ │ └── valid_serializers.py │ │ ├── sql/ │ │ │ ├── check_member_permission_target_exists.sql │ │ │ ├── get_application_user_resource_permission.sql │ │ │ ├── get_knowledge_user_resource_permission.sql │ │ │ ├── get_model_user_resource_permission.sql │ │ │ ├── get_resource_user_permission_detail.sql │ │ │ ├── get_resource_user_permission_detail_ee.sql │ │ │ ├── get_tool_user_resource_permission.sql │ │ │ ├── get_user_resource_permission.sql │ │ │ ├── list_resource_mapping.sql │ │ │ └── list_resource_mapping_ee.sql │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── email_setting.py │ │ ├── log_management.py │ │ ├── resource_mapping.py │ │ ├── system_profile.py │ │ ├── user_resource_permission.py │ │ └── valid.py │ ├── tools/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── tool.py │ │ │ └── tool_workflow.py │ │ ├── apps.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_tool_tool_type.py │ │ │ ├── 0003_alter_tool_template_id.py │ │ │ ├── 0004_alter_tool_tool_type.py │ │ │ ├── 0005_taskrecord.py │ │ │ ├── 0006_alter_tool_tool_type.py │ │ │ ├── 0007_alter_tool_tool_type_toolworkflow_and_more.py │ │ │ ├── __init__.py │ │ │ └── internal_tool.sql │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── tool.py │ │ │ └── tool_workflow.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── tool.py │ │ │ ├── tool_folder.py │ │ │ ├── tool_version.py │ │ │ └── tool_workflow.py │ │ ├── sql/ │ │ │ ├── list_tool.sql │ │ │ ├── list_tool_user.sql │ │ │ └── list_tool_user_ee.sql │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── tool.py │ │ ├── tool_workflow.py │ │ └── tool_workflow_version.py │ ├── trigger/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── trigger.py │ │ │ └── trigger_task.py │ │ ├── apps.py │ │ ├── handler/ │ │ │ ├── base_task.py │ │ │ ├── base_trigger.py │ │ │ ├── impl/ │ │ │ │ ├── task/ │ │ │ │ │ ├── application_task.py │ │ │ │ │ └── tool_task.py │ │ │ │ └── trigger/ │ │ │ │ ├── event_trigger.py │ │ │ │ └── scheduled_trigger.py │ │ │ └── simple_tools.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_remove_taskrecord_trigger_task_and_more.py │ │ │ └── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ └── trigger.py │ │ ├── serializers/ │ │ │ ├── __init__.py │ │ │ ├── task_source_trigger.py │ │ │ ├── trigger.py │ │ │ └── trigger_task.py │ │ ├── sql/ │ │ │ ├── get_trigger_page_list.sql │ │ │ └── get_trigger_task_record_page_list.sql │ │ ├── tasks.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views/ │ │ ├── __init__.py │ │ ├── trigger.py │ │ └── trigger_task.py │ └── users/ │ ├── __init__.py │ ├── admin.py │ ├── api/ │ │ ├── __init__.py │ │ ├── login.py │ │ └── user.py │ ├── apps.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models/ │ │ ├── __init__.py │ │ └── user.py │ ├── serializers/ │ │ ├── __init__.py │ │ ├── login.py │ │ └── user.py │ ├── tests.py │ ├── urls.py │ └── views/ │ ├── __init__.py │ ├── login.py │ └── user.py ├── installer/ │ ├── Dockerfile │ ├── Dockerfile-base │ ├── Dockerfile-vector-model │ ├── init.sql │ ├── install_model.py │ ├── install_model_bert_base_cased.py │ ├── sandbox.c │ ├── start-all.sh │ ├── start-maxkb.sh │ ├── start-postgres.sh │ └── start-redis.sh ├── main.py ├── pyproject.toml └── ui/ ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .prettierrc.json ├── README.md ├── admin.html ├── chat.html ├── env.d.ts ├── eslint.config.ts ├── package.json ├── public/ │ └── tool/ │ ├── bochaai/ │ │ └── detail.md │ ├── google_search/ │ │ └── detail.md │ ├── langsearch/ │ │ └── detail.md │ ├── mysql/ │ │ └── detail.md │ └── postgresql/ │ └── detail.md ├── src/ │ ├── App.vue │ ├── api/ │ │ ├── application/ │ │ │ ├── application-key.ts │ │ │ ├── application.ts │ │ │ ├── chat-log.ts │ │ │ └── workflow-version.ts │ │ ├── chat/ │ │ │ └── chat.ts │ │ ├── chat-user/ │ │ │ ├── auth-setting.ts │ │ │ └── chat-user.ts │ │ ├── image.ts │ │ ├── knowledge/ │ │ │ ├── document.ts │ │ │ ├── knowledge.ts │ │ │ ├── paragraph.ts │ │ │ └── problem.ts │ │ ├── model/ │ │ │ ├── model.ts │ │ │ └── provider.ts │ │ ├── shared-workspace.ts │ │ ├── system/ │ │ │ ├── api-key.ts │ │ │ ├── auth.ts │ │ │ ├── chat-user.ts │ │ │ ├── license.ts │ │ │ ├── operate-log.ts │ │ │ ├── platform-source.ts │ │ │ ├── resource-authorization.ts │ │ │ ├── role.ts │ │ │ ├── user-group.ts │ │ │ ├── user-manage.ts │ │ │ └── workspace.ts │ │ ├── system-resource-management/ │ │ │ ├── application-key.ts │ │ │ ├── application.ts │ │ │ ├── chat-log.ts │ │ │ ├── chat-user.ts │ │ │ ├── document.ts │ │ │ ├── folder.ts │ │ │ ├── knowledge.ts │ │ │ ├── model.ts │ │ │ ├── paragraph.ts │ │ │ ├── problem.ts │ │ │ ├── resource-authorization.ts │ │ │ ├── resource-mapping.ts │ │ │ ├── tool.ts │ │ │ ├── trigger.ts │ │ │ └── workflow-version.ts │ │ ├── system-settings/ │ │ │ ├── auth-setting.ts │ │ │ ├── email-setting.ts │ │ │ ├── platform-source.ts │ │ │ └── theme.ts │ │ ├── system-shared/ │ │ │ ├── authorization.ts │ │ │ ├── chat-user.ts │ │ │ ├── document.ts │ │ │ ├── knowledge.ts │ │ │ ├── model.ts │ │ │ ├── paragraph.ts │ │ │ ├── problem.ts │ │ │ ├── resource-mapping.ts │ │ │ └── tool.ts │ │ ├── tool/ │ │ │ ├── store.ts │ │ │ └── tool.ts │ │ ├── trigger/ │ │ │ └── trigger.ts │ │ ├── type/ │ │ │ ├── application.ts │ │ │ ├── chat.ts │ │ │ ├── common.ts │ │ │ ├── knowledge.ts │ │ │ ├── login.ts │ │ │ ├── model.ts │ │ │ ├── role.ts │ │ │ ├── systemChatUser.ts │ │ │ ├── tool.ts │ │ │ ├── trigger.ts │ │ │ ├── user.ts │ │ │ ├── workspace.ts │ │ │ └── workspaceChatUser.ts │ │ ├── user/ │ │ │ ├── login.ts │ │ │ └── user.ts │ │ └── workspace/ │ │ ├── chat-user.ts │ │ ├── folder.ts │ │ ├── resource-authorization.ts │ │ ├── resource-mapping.ts │ │ ├── role.ts │ │ ├── user-group.ts │ │ └── workspace.ts │ ├── bus/ │ │ └── index.ts │ ├── chat.ts │ ├── components/ │ │ ├── ai-chat/ │ │ │ ├── component/ │ │ │ │ ├── answer-content/ │ │ │ │ │ └── index.vue │ │ │ │ ├── chat-input-operate/ │ │ │ │ │ ├── TouchChat.vue │ │ │ │ │ └── index.vue │ │ │ │ ├── control/ │ │ │ │ │ └── index.vue │ │ │ │ ├── knowledge-source-component/ │ │ │ │ │ ├── ExecutionDetailContent.vue │ │ │ │ │ ├── ParagraphCard.vue │ │ │ │ │ ├── ParagraphDocumentContent.vue │ │ │ │ │ ├── ParagraphSourceContent.vue │ │ │ │ │ └── index.vue │ │ │ │ ├── operation-button/ │ │ │ │ │ ├── ChatOperationButton.vue │ │ │ │ │ ├── LogOperationButton.vue │ │ │ │ │ ├── MobileVoteReasonDrawer.vue │ │ │ │ │ ├── ShareOperationButton.vue │ │ │ │ │ ├── VoteReasonContent.vue │ │ │ │ │ └── index.vue │ │ │ │ ├── prologue-content/ │ │ │ │ │ └── index.vue │ │ │ │ ├── question-content/ │ │ │ │ │ └── index.vue │ │ │ │ ├── transition-content/ │ │ │ │ │ └── index.vue │ │ │ │ └── user-form/ │ │ │ │ └── index.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── app-charts/ │ │ │ ├── components/ │ │ │ │ ├── BarCharts.vue │ │ │ │ └── LineCharts.vue │ │ │ └── index.vue │ │ ├── app-icon/ │ │ │ ├── AppIcon.vue │ │ │ ├── KnowledgeIcon.vue │ │ │ ├── ToolIcon.vue │ │ │ ├── TriggerIcon.vue │ │ │ ├── icons/ │ │ │ │ ├── about.ts │ │ │ │ ├── application.ts │ │ │ │ ├── document.ts │ │ │ │ ├── folder.ts │ │ │ │ ├── knowledge.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── system.ts │ │ │ │ ├── tool.ts │ │ │ │ └── trigger.ts │ │ │ └── index.ts │ │ ├── app-table/ │ │ │ └── index.vue │ │ ├── app-table-infinite-scroll/ │ │ │ └── index.vue │ │ ├── auto-tooltip/ │ │ │ └── index.vue │ │ ├── back-button/ │ │ │ └── index.vue │ │ ├── card-box/ │ │ │ └── index.vue │ │ ├── card-checkbox/ │ │ │ └── index.vue │ │ ├── codemirror-editor/ │ │ │ └── index.vue │ │ ├── common-list/ │ │ │ └── index.vue │ │ ├── dynamics-form/ │ │ │ ├── Demo.vue │ │ │ ├── DemoConstructor.vue │ │ │ ├── FormItem.vue │ │ │ ├── FormItemLabel.vue │ │ │ ├── constructor/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── items/ │ │ │ │ ├── DatePickerConstructor.vue │ │ │ │ ├── JsonInputConstructor.vue │ │ │ │ ├── ModelConstructor.vue │ │ │ │ ├── MultiRowConstructor.vue │ │ │ │ ├── MultiSelectConstructor.vue │ │ │ │ ├── PasswordInputConstructor.vue │ │ │ │ ├── RadioCardConstructor.vue │ │ │ │ ├── RadioRowConstructor.vue │ │ │ │ ├── SingleSelectConstructor.vue │ │ │ │ ├── SliderConstructor.vue │ │ │ │ ├── SwitchInputConstructor.vue │ │ │ │ ├── TextInputConstructor.vue │ │ │ │ ├── TextareaInputConstructor.vue │ │ │ │ └── UploadInputConstructor.vue │ │ │ ├── index.ts │ │ │ ├── index.vue │ │ │ ├── items/ │ │ │ │ ├── DatePicker.vue │ │ │ │ ├── JsonInput.vue │ │ │ │ ├── MultiRow.vue │ │ │ │ ├── PasswordInput.vue │ │ │ │ ├── TextInput.vue │ │ │ │ ├── TextareaInput.vue │ │ │ │ ├── complex/ │ │ │ │ │ ├── ArrayObjectCard.vue │ │ │ │ │ ├── ObjectCard.vue │ │ │ │ │ └── TabCard.vue │ │ │ │ ├── label/ │ │ │ │ │ ├── SettingLabel.vue │ │ │ │ │ └── TooltipLabel.vue │ │ │ │ ├── layout/ │ │ │ │ │ └── RowLayout.vue │ │ │ │ ├── model/ │ │ │ │ │ ├── Model.vue │ │ │ │ │ └── provider-data.ts │ │ │ │ ├── radio/ │ │ │ │ │ ├── Radio.vue │ │ │ │ │ ├── RadioButton.vue │ │ │ │ │ ├── RadioCard.vue │ │ │ │ │ └── RadioRow.vue │ │ │ │ ├── select/ │ │ │ │ │ ├── MultiSelect.vue │ │ │ │ │ └── SingleSelect.vue │ │ │ │ ├── slider/ │ │ │ │ │ └── Slider.vue │ │ │ │ ├── switch/ │ │ │ │ │ └── SwitchInput.vue │ │ │ │ ├── table/ │ │ │ │ │ ├── ProgressTableItem.vue │ │ │ │ │ ├── TableCheckbox.vue │ │ │ │ │ ├── TableColumn.vue │ │ │ │ │ └── TableRadio.vue │ │ │ │ ├── tree/ │ │ │ │ │ └── Tree.vue │ │ │ │ └── upload/ │ │ │ │ ├── LocalFileUpload.vue │ │ │ │ └── UploadInput.vue │ │ │ └── type.ts │ │ ├── execution-detail-card/ │ │ │ └── index.vue │ │ ├── folder-breadcrumb/ │ │ │ └── index.vue │ │ ├── folder-tree/ │ │ │ ├── CreateFolderDialog.vue │ │ │ ├── MoveToDialog.vue │ │ │ ├── constant.ts │ │ │ └── index.vue │ │ ├── generate-related-dialog/ │ │ │ └── index.vue │ │ ├── index.ts │ │ ├── infinite-scroll/ │ │ │ └── index.vue │ │ ├── layout-container/ │ │ │ ├── ContentContainer.vue │ │ │ └── index.vue │ │ ├── loading/ │ │ │ └── DownloadLoading.vue │ │ ├── logo/ │ │ │ ├── LogoFull.vue │ │ │ ├── LogoIcon.vue │ │ │ └── SendIcon.vue │ │ ├── markdown/ │ │ │ ├── EchartsRander.vue │ │ │ ├── FormRander.vue │ │ │ ├── HtmlRander.vue │ │ │ ├── IframeRender.vue │ │ │ ├── MdEditor.vue │ │ │ ├── MdEditorMagnify.vue │ │ │ ├── MdPreview.vue │ │ │ ├── MdRenderer.vue │ │ │ ├── ReasoningRander.vue │ │ │ ├── assets/ │ │ │ │ └── markdown-iconfont.js │ │ │ └── tool-calls-render/ │ │ │ ├── content/ │ │ │ │ ├── index.vue │ │ │ │ └── simple-tool-calls/ │ │ │ │ └── index.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── model-select/ │ │ │ └── index.vue │ │ ├── pdf-export/ │ │ │ └── index.vue │ │ ├── read-write/ │ │ │ └── index.vue │ │ ├── resource-authorization-drawer/ │ │ │ └── index.vue │ │ ├── resource_mapping/ │ │ │ └── index.vue │ │ ├── select-knowledge-document/ │ │ │ └── index.vue │ │ ├── tag-ellipsis/ │ │ │ └── index.vue │ │ ├── tag-group/ │ │ │ └── index.vue │ │ ├── workflow-dropdown-menu/ │ │ │ ├── application/ │ │ │ │ ├── NodeContent.vue │ │ │ │ └── index.vue │ │ │ ├── index.scss │ │ │ ├── index.vue │ │ │ ├── knowledge/ │ │ │ │ ├── NodeContent.vue │ │ │ │ └── index.vue │ │ │ ├── knowledge-inner/ │ │ │ │ ├── NodeContent.vue │ │ │ │ └── index.vue │ │ │ └── tool/ │ │ │ ├── NodeContent.vue │ │ │ └── index.vue │ │ └── workspace-dropdown/ │ │ └── index.vue │ ├── directives/ │ │ ├── clickoutside.ts │ │ ├── hasPermission.ts │ │ ├── index.ts │ │ ├── infiniteScrollUp.ts │ │ └── resize.ts │ ├── enums/ │ │ ├── application.ts │ │ ├── common.ts │ │ ├── document.ts │ │ ├── model.ts │ │ ├── system.ts │ │ ├── tool.ts │ │ └── trigger.ts │ ├── layout/ │ │ ├── app-main/ │ │ │ └── index.vue │ │ ├── components/ │ │ │ ├── breadcrumb/ │ │ │ │ └── index.vue │ │ │ └── sidebar/ │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ ├── hooks/ │ │ │ └── useResize.ts │ │ ├── layout-header/ │ │ │ ├── SystemHeader.vue │ │ │ ├── UserHeader.vue │ │ │ ├── avatar/ │ │ │ │ ├── APIKeyDialog.vue │ │ │ │ ├── AboutDialog.vue │ │ │ │ ├── ResetPassword.vue │ │ │ │ └── index.vue │ │ │ ├── top-about/ │ │ │ │ └── index.vue │ │ │ └── top-menu/ │ │ │ ├── MenuItem.vue │ │ │ └── index.vue │ │ ├── layout-template/ │ │ │ ├── MainLayout.vue │ │ │ ├── SimpleLayout.vue │ │ │ ├── SystemMainLayout.vue │ │ │ └── index.scss │ │ └── login-layout/ │ │ ├── LoginContainer.vue │ │ ├── LoginLayout.vue │ │ └── UserLoginLayout.vue │ ├── locales/ │ │ ├── index.ts │ │ ├── lang/ │ │ │ ├── en-US/ │ │ │ │ ├── ai-chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── components.ts │ │ │ │ ├── dynamics-form.ts │ │ │ │ ├── index.ts │ │ │ │ ├── layout.ts │ │ │ │ ├── theme.ts │ │ │ │ ├── views/ │ │ │ │ │ ├── application-overview.ts │ │ │ │ │ ├── application.ts │ │ │ │ │ ├── chat-log.ts │ │ │ │ │ ├── chat-user.ts │ │ │ │ │ ├── document.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── knowledge.ts │ │ │ │ │ ├── login.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ ├── operate-log.ts │ │ │ │ │ ├── paragraph.ts │ │ │ │ │ ├── problem.ts │ │ │ │ │ ├── role.ts │ │ │ │ │ ├── shared.ts │ │ │ │ │ ├── system.ts │ │ │ │ │ ├── tool.ts │ │ │ │ │ ├── trigger.ts │ │ │ │ │ ├── user-manage.ts │ │ │ │ │ └── workspace.ts │ │ │ │ └── workflow.ts │ │ │ ├── zh-CN/ │ │ │ │ ├── ai-chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── components.ts │ │ │ │ ├── dynamics-form.ts │ │ │ │ ├── index.ts │ │ │ │ ├── layout.ts │ │ │ │ ├── theme.ts │ │ │ │ ├── views/ │ │ │ │ │ ├── application-overview.ts │ │ │ │ │ ├── application.ts │ │ │ │ │ ├── chat-log.ts │ │ │ │ │ ├── chat-user.ts │ │ │ │ │ ├── document.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── knowledge.ts │ │ │ │ │ ├── login.ts │ │ │ │ │ ├── model.ts │ │ │ │ │ ├── operate-log.ts │ │ │ │ │ ├── paragraph.ts │ │ │ │ │ ├── problem.ts │ │ │ │ │ ├── role.ts │ │ │ │ │ ├── shared.ts │ │ │ │ │ ├── system.ts │ │ │ │ │ ├── tool.ts │ │ │ │ │ ├── trigger.ts │ │ │ │ │ ├── user-manage.ts │ │ │ │ │ └── workspace.ts │ │ │ │ └── workflow.ts │ │ │ └── zh-Hant/ │ │ │ ├── ai-chat.ts │ │ │ ├── common.ts │ │ │ ├── components.ts │ │ │ ├── dynamics-form.ts │ │ │ ├── index.ts │ │ │ ├── layout.ts │ │ │ ├── theme.ts │ │ │ ├── views/ │ │ │ │ ├── application-overview.ts │ │ │ │ ├── application.ts │ │ │ │ ├── chat-log.ts │ │ │ │ ├── chat-user.ts │ │ │ │ ├── document.ts │ │ │ │ ├── index.ts │ │ │ │ ├── knowledge.ts │ │ │ │ ├── login.ts │ │ │ │ ├── model.ts │ │ │ │ ├── operate-log.ts │ │ │ │ ├── paragraph.ts │ │ │ │ ├── problem.ts │ │ │ │ ├── role.ts │ │ │ │ ├── shared.ts │ │ │ │ ├── system.ts │ │ │ │ ├── tool.ts │ │ │ │ ├── trigger.ts │ │ │ │ ├── user-manage.ts │ │ │ │ └── workspace.ts │ │ │ └── workflow.ts │ │ └── useLocale.ts │ ├── main.ts │ ├── permission/ │ │ ├── application/ │ │ │ ├── index.ts │ │ │ ├── system-manage.ts │ │ │ └── workspace.ts │ │ ├── index.ts │ │ ├── knowledge/ │ │ │ ├── index.ts │ │ │ ├── system-manage.ts │ │ │ ├── system-share.ts │ │ │ ├── workspace-share.ts │ │ │ └── workspace.ts │ │ ├── model/ │ │ │ ├── index.ts │ │ │ ├── system-manage.ts │ │ │ ├── system-share.ts │ │ │ └── workspace.ts │ │ └── tool/ │ │ ├── index.ts │ │ ├── system-manage.ts │ │ ├── system-share.ts │ │ └── workspace.ts │ ├── request/ │ │ ├── Result.ts │ │ ├── chat/ │ │ │ ├── Result.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── router/ │ │ ├── chat/ │ │ │ ├── index.ts │ │ │ └── routes.ts │ │ ├── common.ts │ │ ├── index.ts │ │ ├── modules/ │ │ │ ├── application-detail.ts │ │ │ ├── application.ts │ │ │ ├── document.ts │ │ │ ├── knowledge.ts │ │ │ ├── model.ts │ │ │ ├── paragraph.ts │ │ │ ├── system.ts │ │ │ ├── tool.ts │ │ │ └── trigger.ts │ │ └── routes.ts │ ├── stores/ │ │ ├── index.ts │ │ └── modules/ │ │ ├── application.ts │ │ ├── chat-user.ts │ │ ├── common.ts │ │ ├── folder.ts │ │ ├── knowledge.ts │ │ ├── login.ts │ │ ├── model.ts │ │ ├── prompt.ts │ │ ├── theme.ts │ │ ├── tool.ts │ │ └── user.ts │ ├── styles/ │ │ ├── app.scss │ │ ├── component.scss │ │ ├── element-plus.scss │ │ ├── font/ │ │ │ └── AlibabaPuHuiTi-3-55-Regular.otf │ │ ├── index.scss │ │ ├── md-editor.scss │ │ └── variables.scss │ ├── utils/ │ │ ├── application.ts │ │ ├── array.ts │ │ ├── bus.ts │ │ ├── clipboard.ts │ │ ├── common.ts │ │ ├── dynamics-api/ │ │ │ ├── permission-api.ts │ │ │ └── shared-api.ts │ │ ├── folder.ts │ │ ├── message.ts │ │ ├── permission/ │ │ │ ├── data.ts │ │ │ ├── index.ts │ │ │ └── type.ts │ │ ├── status.ts │ │ ├── theme.ts │ │ ├── time.ts │ │ └── trigger.ts │ ├── views/ │ │ ├── Permission.vue │ │ ├── application/ │ │ │ ├── ApplicationAccess.vue │ │ │ ├── ApplicationSetting.vue │ │ │ ├── component/ │ │ │ │ ├── AIModeParamSettingDialog.vue │ │ │ │ ├── AccessSettingDrawer.vue │ │ │ │ ├── AddKnowledgeDialog.vue │ │ │ │ ├── ApplicationDialog.vue │ │ │ │ ├── CopyApplicationDialog.vue │ │ │ │ ├── CreateApplicationDialog.vue │ │ │ │ ├── GeneratePromptDialog.vue │ │ │ │ ├── McpServersDialog.vue │ │ │ │ ├── ParamSettingDialog.vue │ │ │ │ ├── ReasoningParamSettingDialog.vue │ │ │ │ ├── STTModelParamSettingDialog.vue │ │ │ │ ├── TTSModeParamSettingDialog.vue │ │ │ │ └── ToolDialog.vue │ │ │ ├── index.vue │ │ │ └── template-store/ │ │ │ ├── InternalDescDrawer.vue │ │ │ ├── TemplateCard.vue │ │ │ └── TemplateStoreDialog.vue │ │ ├── application-overview/ │ │ │ ├── component/ │ │ │ │ ├── APIKeyDialog.vue │ │ │ │ ├── DisplaySettingDialog.vue │ │ │ │ ├── EmbedDialog.vue │ │ │ │ ├── LimitDialog.vue │ │ │ │ ├── SettingAPIKeyDialog.vue │ │ │ │ ├── SettingAPIKeyDrawer.vue │ │ │ │ └── StatisticsCharts.vue │ │ │ ├── index.vue │ │ │ └── xpack-component/ │ │ │ ├── XPackDisplaySettingDialog.vue │ │ │ └── XPackLimitDrawer.vue │ │ ├── application-workflow/ │ │ │ ├── component/ │ │ │ │ └── PublishHistory.vue │ │ │ └── index.vue │ │ ├── chat/ │ │ │ ├── Share.vue │ │ │ ├── auth/ │ │ │ │ ├── component/ │ │ │ │ │ └── password.vue │ │ │ │ └── index.vue │ │ │ ├── component/ │ │ │ │ ├── EditTitleDialog.vue │ │ │ │ └── HistoryPanel.vue │ │ │ ├── embed/ │ │ │ │ ├── component/ │ │ │ │ │ └── ChatHistoryDrawer.vue │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ ├── mobile/ │ │ │ │ ├── component/ │ │ │ │ │ ├── ChatHistoryDrawer.vue │ │ │ │ │ ├── ResetPasswordDrawer.vue │ │ │ │ │ └── UserCenterDrawer.vue │ │ │ │ └── index.vue │ │ │ ├── no-service/ │ │ │ │ └── index.vue │ │ │ ├── pc/ │ │ │ │ └── index.vue │ │ │ └── user-login/ │ │ │ ├── index.vue │ │ │ └── scanCompinents/ │ │ │ ├── QrCodeTab.vue │ │ │ ├── dingtalkQrCode.vue │ │ │ ├── larkQrCode.vue │ │ │ └── wecomQrCode.vue │ │ ├── chat-log/ │ │ │ ├── component/ │ │ │ │ ├── ChatRecordDrawer.vue │ │ │ │ ├── EditContentDialog.vue │ │ │ │ └── EditMarkDialog.vue │ │ │ └── index.vue │ │ ├── chat-user/ │ │ │ └── index.vue │ │ ├── demo/ │ │ │ └── index.vue │ │ ├── document/ │ │ │ ├── ImportLarkDocument.vue │ │ │ ├── ImportWorkflowDocument.vue │ │ │ ├── UploadDocument.vue │ │ │ ├── component/ │ │ │ │ ├── EmbeddingContentDialog.vue │ │ │ │ ├── ImportDocumentDialog.vue │ │ │ │ ├── SelectKnowledgeDialog.vue │ │ │ │ ├── Status.vue │ │ │ │ └── StatusTable.vue │ │ │ ├── index.scss │ │ │ ├── index.vue │ │ │ ├── tag/ │ │ │ │ ├── CreateTagDialog.vue │ │ │ │ ├── EditTagDialog.vue │ │ │ │ ├── MulAddTagDialog.vue │ │ │ │ ├── TagDrawer.vue │ │ │ │ ├── TagSettingDrawer.vue │ │ │ │ └── TaglinkedDocumentDialog.vue │ │ │ └── upload/ │ │ │ ├── ResultSuccess.vue │ │ │ ├── SetRules.vue │ │ │ └── UploadComponent.vue │ │ ├── error/ │ │ │ ├── 404.vue │ │ │ ├── NoPermission.vue │ │ │ └── NoService.vue │ │ ├── hit-test/ │ │ │ └── index.vue │ │ ├── knowledge/ │ │ │ ├── KnowledgeSetting.vue │ │ │ ├── WorkflowTransform.vue │ │ │ ├── component/ │ │ │ │ ├── BaseForm.vue │ │ │ │ ├── EditParagraphDialog.vue │ │ │ │ ├── KnowledgeListContainer.vue │ │ │ │ ├── ParagraphList.vue │ │ │ │ ├── ParagraphPreview.vue │ │ │ │ └── SyncWebDialog.vue │ │ │ ├── create-component/ │ │ │ │ ├── CreateKnowledgeDialog.vue │ │ │ │ ├── CreateLarkKnowledgeDialog.vue │ │ │ │ ├── CreateWebKnowledgeDialog.vue │ │ │ │ └── CreateWorkflowKnowledgeDialog.vue │ │ │ ├── index.vue │ │ │ └── template-store/ │ │ │ ├── InternalDescDrawer.vue │ │ │ ├── TemplateCard.vue │ │ │ └── TemplateStoreDialog.vue │ │ ├── knowledge-workflow/ │ │ │ ├── component/ │ │ │ │ ├── DebugDrawer.vue │ │ │ │ ├── PublishHistory.vue │ │ │ │ ├── action/ │ │ │ │ │ ├── DataSource.vue │ │ │ │ │ ├── KnowledgeBase.vue │ │ │ │ │ └── Result.vue │ │ │ │ └── execution-record/ │ │ │ │ ├── ExecutionDetailDrawer.vue │ │ │ │ └── ExecutionRecordDrawer.vue │ │ │ └── index.vue │ │ ├── login/ │ │ │ ├── ForgotPassword.vue │ │ │ ├── ResetPassword.vue │ │ │ ├── index.vue │ │ │ └── scanCompinents/ │ │ │ ├── QrCodeTab.vue │ │ │ ├── dingtalkQrCode.vue │ │ │ ├── larkQrCode.vue │ │ │ └── wecomQrCode.vue │ │ ├── model/ │ │ │ ├── component/ │ │ │ │ ├── AddParamDrawer.vue │ │ │ │ ├── CreateModelDialog.vue │ │ │ │ ├── EditModel.vue │ │ │ │ ├── ModelCard.vue │ │ │ │ ├── ParamSettingDialog.vue │ │ │ │ ├── Provider.vue │ │ │ │ ├── SelectProviderDialog.vue │ │ │ │ └── data.ts │ │ │ └── index.vue │ │ ├── paragraph/ │ │ │ ├── component/ │ │ │ │ ├── ParagraphCard.vue │ │ │ │ ├── ParagraphDialog.vue │ │ │ │ ├── ParagraphForm.vue │ │ │ │ ├── ProblemComponent.vue │ │ │ │ └── SelectDocumentDialog.vue │ │ │ └── index.vue │ │ ├── problem/ │ │ │ ├── component/ │ │ │ │ ├── CreateProblemDialog.vue │ │ │ │ ├── DetailProblemDrawer.vue │ │ │ │ └── RelateProblemDialog.vue │ │ │ └── index.vue │ │ ├── system/ │ │ │ ├── operate-log/ │ │ │ │ ├── component/ │ │ │ │ │ └── DetailDialog.vue │ │ │ │ └── index.vue │ │ │ ├── resource-authorization/ │ │ │ │ ├── component/ │ │ │ │ │ └── PermissionTable.vue │ │ │ │ ├── constant.ts │ │ │ │ └── index.vue │ │ │ ├── role/ │ │ │ │ ├── component/ │ │ │ │ │ ├── AddMemberDrawer.vue │ │ │ │ │ ├── CreateOrUpdateRoleDialog.vue │ │ │ │ │ ├── Member.vue │ │ │ │ │ ├── MemberFormContent.vue │ │ │ │ │ └── PermissionConfiguration.vue │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── user-manage/ │ │ │ │ ├── component/ │ │ │ │ │ ├── SetUserRoleDialog.vue │ │ │ │ │ ├── UserDrawer.vue │ │ │ │ │ └── UserPwdDialog.vue │ │ │ │ └── index.vue │ │ │ └── workspace/ │ │ │ ├── component/ │ │ │ │ ├── AddMemberDrawer.vue │ │ │ │ ├── CreateOrUpdateWorkspaceDialog.vue │ │ │ │ └── Member.vue │ │ │ └── index.vue │ │ ├── system-chat-user/ │ │ │ ├── authentication/ │ │ │ │ ├── component/ │ │ │ │ │ ├── CAS.vue │ │ │ │ │ ├── EditModal.vue │ │ │ │ │ ├── LDAP.vue │ │ │ │ │ ├── OAuth2.vue │ │ │ │ │ ├── OIDC.vue │ │ │ │ │ └── SCAN.vue │ │ │ │ └── index.vue │ │ │ ├── chat-user/ │ │ │ │ ├── component/ │ │ │ │ │ ├── SetUserGroupsDialog.vue │ │ │ │ │ ├── SyncUsersDialog.vue │ │ │ │ │ ├── UserDrawer.vue │ │ │ │ │ └── UserPwdDialog.vue │ │ │ │ └── index.vue │ │ │ └── group/ │ │ │ ├── component/ │ │ │ │ ├── CreateGroupUserDialog.vue │ │ │ │ └── CreateOrUpdateGroupDialog.vue │ │ │ └── index.vue │ │ ├── system-resource-management/ │ │ │ ├── ApplicationResourceIndex.vue │ │ │ ├── KnowledgeResourceIndex.vue │ │ │ ├── ModelResourceIndex.vue │ │ │ └── ToolResourceIndex.vue │ │ ├── system-setting/ │ │ │ ├── authentication/ │ │ │ │ ├── component/ │ │ │ │ │ ├── CAS.vue │ │ │ │ │ ├── EditModal.vue │ │ │ │ │ ├── LDAP.vue │ │ │ │ │ ├── OAuth2.vue │ │ │ │ │ ├── OIDC.vue │ │ │ │ │ ├── SCAN.vue │ │ │ │ │ ├── Saml2.vue │ │ │ │ │ └── Setting.vue │ │ │ │ └── index.vue │ │ │ ├── email/ │ │ │ │ └── index.vue │ │ │ └── theme/ │ │ │ ├── LoginPreview.vue │ │ │ └── index.vue │ │ ├── system-shared/ │ │ │ ├── AuthorizedWorkspaceDialog.vue │ │ │ ├── KnowLedgeSharedIndex.vue │ │ │ ├── ModelSharedIndex.vue │ │ │ └── ToolSharedIndex.vue │ │ ├── tool/ │ │ │ ├── DataSourceToolFormDrawer.vue │ │ │ ├── McpToolFormDrawer.vue │ │ │ ├── SkillToolFormDrawer.vue │ │ │ ├── ToolDebugDrawer.vue │ │ │ ├── ToolFormDrawer.vue │ │ │ ├── WorkflowFormDialog.vue │ │ │ ├── component/ │ │ │ │ ├── EditAvatarDialog.vue │ │ │ │ ├── FieldFormDialog.vue │ │ │ │ ├── InitParamDrawer.vue │ │ │ │ ├── McpToolConfigDialog.vue │ │ │ │ ├── ToolListContainer.vue │ │ │ │ ├── ToolStoreDescDrawer.vue │ │ │ │ └── UserFieldFormDialog.vue │ │ │ ├── execution-record/ │ │ │ │ ├── ExecutionDetailDrawer.vue │ │ │ │ └── TriggerRecordDrawer.vue │ │ │ ├── index.vue │ │ │ └── tool-store/ │ │ │ ├── AddInternalToolDialog.vue │ │ │ ├── InternalDescDrawer.vue │ │ │ ├── ToolCard.vue │ │ │ └── ToolStoreDialog.vue │ │ ├── tool-workflow/ │ │ │ ├── component/ │ │ │ │ ├── PublishHistory.vue │ │ │ │ ├── debug/ │ │ │ │ │ ├── index.vue │ │ │ │ │ ├── parameters/ │ │ │ │ │ │ └── index.vue │ │ │ │ │ └── result/ │ │ │ │ │ └── index.vue │ │ │ │ └── debug-drawer/ │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ └── trigger/ │ │ ├── ResourceTriggerDrawer.vue │ │ ├── TriggerDrawer.vue │ │ ├── component/ │ │ │ ├── ApplicationParameter.vue │ │ │ └── ToolParameter.vue │ │ ├── execution-record/ │ │ │ ├── ExecutionDetailDrawer.vue │ │ │ └── TriggerTaskRecordDrawer.vue │ │ └── index.vue │ └── workflow/ │ ├── common/ │ │ ├── AddFormCollect.vue │ │ ├── CustomLine.vue │ │ ├── EditFormCollect.vue │ │ ├── NodeCascader.vue │ │ ├── NodeContainer.vue │ │ ├── NodeControl.vue │ │ ├── NodeSearch.vue │ │ ├── app-node.ts │ │ ├── data.ts │ │ ├── edge.ts │ │ ├── loopEdge.ts │ │ ├── shortcut.ts │ │ ├── teleport.ts │ │ ├── template.ts │ │ └── validate.ts │ ├── icons/ │ │ ├── ai-chat-node-icon.vue │ │ ├── application-node-icon.vue │ │ ├── base-node-icon.vue │ │ ├── chat-icon.vue │ │ ├── condition-node-icon.vue │ │ ├── data-source-local-node-icon.vue │ │ ├── data-source-web-node-icon.vue │ │ ├── document-extract-node-icon.vue │ │ ├── document-split-node-icon.vue │ │ ├── form-node-icon.vue │ │ ├── global-icon.vue │ │ ├── image-generate-node-icon.vue │ │ ├── image-to-video-node-icon.vue │ │ ├── image-understand-node-icon.vue │ │ ├── intent-node-icon.vue │ │ ├── knowledge-base-node-icon.vue │ │ ├── knowledge-write-node-icon.vue │ │ ├── loop-break-node-icon.vue │ │ ├── loop-continue-node-icon.vue │ │ ├── loop-icon.vue │ │ ├── loop-node-icon.vue │ │ ├── loop-start-node-icon.vue │ │ ├── mcp-node-icon.vue │ │ ├── output-icon.vue │ │ ├── parameter-extraction-node-icon.vue │ │ ├── question-node-icon.vue │ │ ├── reply-node-icon.vue │ │ ├── reranker-node-icon.vue │ │ ├── search-document-node-icon.vue │ │ ├── search-knowledge-node-icon.vue │ │ ├── speech-to-text-node-icon.vue │ │ ├── start-node-icon.vue │ │ ├── text-to-speech-node-icon.vue │ │ ├── text-to-video-node-icon.vue │ │ ├── tool-base-node-icon.vue │ │ ├── tool-lib-node-icon.vue │ │ ├── tool-node-icon.vue │ │ ├── tool-start-node-icon.vue │ │ ├── tool-workflow-lib-node-icon.vue │ │ ├── utils.ts │ │ ├── variable-aggregation-node-icon.vue │ │ ├── variable-assign-node-icon.vue │ │ ├── variable-splitting-node-icon.vue │ │ └── video-understand-node-icon.vue │ ├── index.vue │ ├── nodes/ │ │ ├── ai-chat-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── application-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── base-node/ │ │ │ ├── component/ │ │ │ │ ├── ApiFieldFormDialog.vue │ │ │ │ ├── ApiInputFieldTable.vue │ │ │ │ ├── ChatFieldDialog.vue │ │ │ │ ├── ChatFieldTable.vue │ │ │ │ ├── FileUploadSettingDialog.vue │ │ │ │ ├── UserFieldFormDialog.vue │ │ │ │ ├── UserInputFieldTable.vue │ │ │ │ └── UserInputTitleDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── condition-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── data-source-local-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── data-source-web-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── document-extract-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── document-split-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── form-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── image-generate/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── image-to-video/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── image-understand/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── intent-classify-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── knowledge-base-node/ │ │ │ ├── component/ │ │ │ │ ├── UserFieldFormDialog.vue │ │ │ │ ├── UserInputFieldTable.vue │ │ │ │ └── UserInputTitleDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── knowledge-write-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── loop-body-node/ │ │ │ ├── LoopBodyContainer.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── loop-break-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── loop-continue-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── loop-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── loop-start-node/ │ │ │ ├── component/ │ │ │ │ ├── LoopFieldDialog.vue │ │ │ │ └── LoopFieldTable.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── mcp-node/ │ │ │ ├── component/ │ │ │ │ └── McpServerInputDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── parameter-extraction-node/ │ │ │ ├── component/ │ │ │ │ ├── ParametersFieldDialog.vue │ │ │ │ └── ParametersFieldTable.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── question-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── reply-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── reranker-node/ │ │ │ ├── ParamSettingDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── search-document-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── search-knowledge-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── speech-to-text-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── start-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── text-to-speech-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── text-to-video/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── tool-base-node/ │ │ │ ├── component/ │ │ │ │ ├── input/ │ │ │ │ │ ├── InputFieldFormDialog.vue │ │ │ │ │ ├── InputFieldTable.vue │ │ │ │ │ └── InputTitleDialog.vue │ │ │ │ └── output/ │ │ │ │ ├── OutputFieldFormDialog.vue │ │ │ │ ├── OutputFieldTable.vue │ │ │ │ └── OutputTitleDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── tool-lib-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── tool-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── tool-start-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── tool-workflow-lib-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── variable-aggregation-node/ │ │ │ ├── component/ │ │ │ │ └── GroupFieldDialog.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── variable-assign-node/ │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── variable-splitting/ │ │ │ ├── component/ │ │ │ │ ├── VariableFieldDialog.vue │ │ │ │ └── VariableFieldTable.vue │ │ │ ├── index.ts │ │ │ └── index.vue │ │ └── video-understand/ │ │ ├── index.ts │ │ └── index.vue │ └── plugins/ │ └── dagre.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .git* .idea* *.md .venv/ ================================================ FILE: .editorconfig ================================================ root = true [*.py] max_line_length = 120 ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: 'Bug Report' description: 'Report an Bug' title: "[Bug] " assignees: zyyfit body: - type: markdown attributes: value: "## Contact Information" - type: input validations: required: false attributes: label: "Contact Information" description: "The ways to quickly contact you: WeChat group number and nickname, email, etc." - type: markdown attributes: value: "## Environment Information" - type: input validations: required: true attributes: label: "MaxKB Version" description: "Log in to the MaxKB Web Console and check the current version on the `About` page in the top right corner." - type: markdown attributes: value: "## Detailed information" - type: textarea attributes: label: "Problem Description" description: "Briefly describe the issue you’ve encountered." validations: required: true - type: textarea attributes: label: "Steps to Reproduce" description: "How can this issue be reproduced." validations: required: true - type: textarea attributes: label: "The expected correct result" - type: textarea attributes: label: "Related log output" description: "Please paste any relevant log output here. It will automatically be formatted as code, so no backticks are necessary." render: shell - type: textarea attributes: label: "Additional Information" description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)." ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: 'Feature Request' description: 'Suggest an idea' title: '[Feature] ' assignees: baixin513 body: - type: markdown attributes: value: "## Environment Information" - type: input validations: required: true attributes: label: "MaxKB Version" description: "Log in to the MaxKB Web Console and check the current version on the `About` page in the top right corner." - type: markdown attributes: value: "## Detailed information" - type: textarea attributes: label: "Please describe your needs or suggestions for improvements" validations: required: true - type: textarea attributes: label: "Please describe the solution you suggest" - type: textarea attributes: label: "Additional Information" description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)." ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ #### What this PR does / why we need it? #### Summary of your change #### Please indicate you've done the following: - [ ] Made sure tests are passing and test coverage is added if needed. - [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/). - [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" timezone: "Asia/Shanghai" day: "friday" target-branch: "v3" ================================================ FILE: .github/workflows/build-and-push-python-pg.yml ================================================ name: build-and-push-python-pg on: workflow_dispatch: inputs: architecture: description: 'Architecture' required: true default: 'linux/amd64' type: choice options: - linux/amd64 - linux/arm64 - linux/amd64,linux/arm64 jobs: build-and-push-python-pg-to-ghcr: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} - name: Prepare id: prepare run: | DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-base DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} TAG_NAME=python3.11-pg17.7-20260212 DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}" echo ::set-output name=docker_image::${DOCKER_IMAGE} echo ::set-output name=version::${TAG_NAME} echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \ --build-arg VERSION=${TAG_NAME} \ --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ --build-arg VCS_REF=${GITHUB_SHA::8} \ ${DOCKER_IMAGE_TAGS} . - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Docker Buildx (build-and-push) run: | docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-base ================================================ FILE: .github/workflows/build-and-push-vector-model.yml ================================================ name: build-and-push-vector-model on: workflow_dispatch: inputs: dockerImageTag: description: 'Docker Image Tag' default: 'v2.0.3' required: true architecture: description: 'Architecture' required: true default: 'linux/amd64' type: choice options: - linux/amd64 - linux/arm64 - linux/amd64,linux/arm64 jobs: build-and-push-vector-model-to-ghcr: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} - name: Prepare id: prepare run: | DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-vector-model DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} TAG_NAME=${{ github.event.inputs.dockerImageTag }} DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest" echo ::set-output name=docker_image::${DOCKER_IMAGE} echo ::set-output name=version::${TAG_NAME} echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \ --build-arg VERSION=${TAG_NAME} \ --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ --build-arg VCS_REF=${GITHUB_SHA::8} \ ${DOCKER_IMAGE_TAGS} . - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Docker Buildx (build-and-push) run: | docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-vector-model ================================================ FILE: .github/workflows/build-and-push.yml ================================================ name: build-and-push run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }}) (${{ github.event.inputs.architecture }}) on: workflow_dispatch: inputs: dockerImageTag: description: 'Image Tag' default: 'v2.6.0-dev' required: true dockerImageTagWithLatest: description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)' default: false required: true type: boolean architecture: description: 'Architecture' required: true default: 'linux/amd64' type: choice options: - linux/amd64 - linux/arm64 - linux/amd64,linux/arm64 registry: description: 'Push To Registry' required: true default: 'fit2cloud-registry' type: choice options: - fit2cloud-registry - dockerhub - dockerhub, fit2cloud-registry jobs: build-and-push-to-fit2cloud-registry: if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }} runs-on: ubuntu-latest steps: - name: Clear Work Dir run: | ls -la rm -rf -- ./* ./.??* - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} - name: Prepare id: prepare run: | DOCKER_IMAGE=${{ secrets.FIT2CLOUD_REGISTRY_HOST }}/maxkb/maxkb DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} TAG_NAME=${{ github.event.inputs.dockerImageTag }} TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }} if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest" else DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}" fi echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \ --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \ ${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Login to FIT2CLOUD Registry uses: docker/login-action@v3 with: registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }} username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }} password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }} - name: Build Web run: | docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile rm -rf ./ui cp -r ./web-build-output/ui ./ rm -rf ./web-build-output - name: Docker Buildx (build-and-push) run: | sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile build-and-push-to-dockerhub: if: ${{ contains(github.event.inputs.registry, 'dockerhub') }} runs-on: ubuntu-latest steps: - name: Clear Work Dir run: | ls -la rm -rf -- ./* ./.??* - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} - name: Prepare id: prepare run: | DOCKER_IMAGE=1panel/maxkb DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} TAG_NAME=${{ github.event.inputs.dockerImageTag }} TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }} if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest" else DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}" fi echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \ --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \ ${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build Web run: | docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile rm -rf ./ui cp -r ./web-build-output/ui ./ rm -rf ./web-build-output - name: Docker Buildx (build-and-push) run: | sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile ================================================ FILE: .github/workflows/create-pr-from-push.yml ================================================ on: push: branches: - 'pr@**' - 'repr@**' name: 针对特定分支名自动创建 PR jobs: generic_handler: name: 自动创建 PR runs-on: ubuntu-latest steps: - name: Create pull request uses: jumpserver/action-generic-handler@master env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} ================================================ FILE: .github/workflows/issue-translator.yml ================================================ name: Issue Translator on: issue_comment: types: [created] issues: types: [opened] jobs: build: runs-on: ubuntu-latest steps: - uses: usthe/issues-translate-action@v2.7 with: IS_MODIFY_TITLE: true BOT_GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }} ================================================ FILE: .github/workflows/llm-code-review.yml ================================================ name: LLM Code Review permissions: contents: read pull-requests: write on: pull_request: types: [opened, reopened, synchronize] jobs: llm-code-review: runs-on: ubuntu-latest steps: - uses: fit2cloud/LLM-CodeReview-Action@main env: GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }} OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }} LANGUAGE: English OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1 MODEL: qwen2.5-coder-3b-instruct PROMPT: "Please check the following code for any irregularities, potential issues, or optimization suggestions, and provide your answers in English." top_p: 1 temperature: 1 # max_tokens: 10000 MAX_PATCH_LENGTH: 10000 IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github" FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html" ================================================ FILE: .github/workflows/sync2gitee.yml ================================================ name: sync2gitee on: [push] jobs: repo-sync: runs-on: ubuntu-latest steps: - name: Mirror the Github organization repos to Gitee. uses: Yikun/hub-mirror-action@master with: src: 'github/1Panel-dev' dst: 'gitee/fit2cloud-feizhiyun' dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} dst_token: ${{ secrets.GITEE_TOKEN }} static_list: "MaxKB" force_update: true ================================================ FILE: .github/workflows/typos_check.yml ================================================ name: Typos Check on: workflow_dispatch: push: pull_request: types: [opened, synchronize, reopened] jobs: run: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} - name: Create config file run: | cat < typo-check-config.toml [files] extend-exclude = [ "**/*_svg", "**/migrations/**", "**/loopEdge.ts", "**/edge.ts", ] EOF - name: Check spelling uses: crate-ci/typos@master with: config: ./typo-check-config.toml ================================================ FILE: .gitignore ================================================ # Mac .DS_Store */.DS_Store # VS Code .vscode *.project *.factorypath # IntelliJ IDEA .idea/* !.idea/icon.png *.iws *.iml *.ipr # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script forms a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv # env/ venv/ # ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ui/package-lock.json ui/node_modules ui/dist apps/static models/ apps/xpack !apps/**/models/ data .dev poetry.lock uv.lock apps/models_provider/impl/*/icon/ apps/models_provider/impl/tencent_model_provider/credential/stt.py apps/models_provider/impl/tencent_model_provider/model/stt.py tmp/ config.yml .SANDBOX_BANNED_HOSTS ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@fit2cloud.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing As a contributor, you should agree that: - The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. - Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. ## Create pull request PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it. Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot of features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion. This [development guideline](https://github.com/1Panel-dev/MaxKB/wiki/3-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA) contains information about repository structure, how to set up development environment, how to run it, and more. Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete. ## Report issues It is a great way to contribute by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue and follow the template to fill in required information. Before opening any issue, please look up the existing issues to avoid submitting a duplication. If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment. When reporting issues, always include: * Which version you are using. * Steps to reproduce the issue. * Snapshots or log files if needed Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can replace those parts with "REDACTED" or other strings like "****". ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================

MaxKB

Open-source platform for building enterprise-grade agents

强大易用的企业级智能体平台

1Panel-dev%2FMaxKB | Trendshift

License: GPL v3 Latest release Stars Download
[中文(简体)] | [English]


MaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education. - **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience. - **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios. - **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction. - **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.). - **Multi Modal**: Native support for input and output text, image, audio and video. ## Quick start Execute the script below to start a MaxKB container using Docker: ```bash docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb 1panel/maxkb ``` Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials: - username: admin - password: MaxKB@123.. 中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/v2/installation/offline_installtion/) 进行安装。 ## Screenshots
MaxKB Demo1 MaxKB Demo2
MaxKB Demo3 MaxKB Demo4
## Technical stack - Frontend:[Vue.js](https://vuejs.org/) - Backend:[Python / Django](https://www.djangoproject.com/) - LLM Framework:[LangChain](https://www.langchain.com/) - Database:[PostgreSQL + pgvector](https://www.postgresql.org/) ## 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) ## License Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README_CN.md ================================================

MaxKB

强大易用的企业级智能体平台

1Panel-dev%2FMaxKB | Trendshift

English README License: GPL v3 Latest release Stars Download Gitee Stars GitCode Stars


MaxKB = Max Knowledge Brain,是一个强大易用的企业级智能体平台,致力于解决企业 AI 落地面临的技术门槛高、部署成本高、迭代周期长等问题,助力企业在人工智能时代赢得先机。秉承“开箱即用,伴随成长”的设计理念,MaxKB 支持企业快速接入主流大模型,高效构建专属知识库,并提供从基础问答(RAG)、复杂流程自动化(工作流)到智能体(Agent)的渐进式升级路径,全面赋能智能客服、智能办公助手等多种应用场景。 - **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果; - **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求; - **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度; - **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Qwen 3 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。 MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/ ## 快速开始 ``` # Linux 机器 docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb # Windows 机器 docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb # 用户名: admin # 密码: MaxKB@123.. ``` - 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB; - 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署; - MaxKB 不同产品产品版本的对比请参见:[MaxKB 产品版本对比](https://maxkb.cn/price); - 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://fit2cloud.com/maxkb/download/introduce-maxkb_2026.pdf)。 如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。 - [案例展示](USE-CASES.md) - [使用手册](https://maxkb.cn/docs/) - [论坛求助](https://bbs.fit2cloud.com/c/mk/11) - 技术交流群 ## UI 展示
MaxKB Demo1 MaxKB Demo2
MaxKB Demo3 MaxKB Demo4
## 技术栈 - 前端:[Vue.js](https://cn.vuejs.org/) - 后端:[Python / Django](https://www.djangoproject.com/) - LangChain:[LangChain](https://www.langchain.com/) - 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/) ## 飞致云的其他明星项目 - [Cordys CRM](https://github.com/1Panel-dev/CordysCRM) - 新一代的开源 AI CRM 系统 - [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板 - [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机 - [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具 - [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具 - [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具 ## License Copyright (c) 2014-2026 飞致云 FIT2CLOUD, All rights reserved. Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: SECURITY.md ================================================ # 安全说明 如果您发现安全问题,请直接联系我们: - support@fit2cloud.com - 400-052-0755 感谢您的支持! # Security Policy All security bugs should be reported to the contact as below: - support@fit2cloud.com - 400-052-0755 Thanks for your support! ================================================ FILE: USE-CASES.md ================================================

MaxKB 应用案例,持续更新中...

------------------------------ - [MaxKB 应用案例:中国农业大学-小鹉哥](https://mp.weixin.qq.com/s/4g_gySMBQZCJ9OZ-yBkmvw) - [MaxKB 应用案例:东北财经大学-小银杏](https://mp.weixin.qq.com/s/3BoxkY7EMomMmmvFYxvDIA) - [MaxKB 应用案例:中铁水务](https://mp.weixin.qq.com/s/voNAddbK2CJOrJJs1ewZ8g) - [MaxKB 应用案例:解放军总医院](https://mp.weixin.qq.com/s/ETrZC-vrA4Aap0eF-15EeQ) - [MaxKB 应用案例:无锡市数据局](https://mp.weixin.qq.com/s/enfUFLevvL_La74PQ0kIXw) - [MaxKB 应用案例:中核西仪研究院-西仪睿答](https://mp.weixin.qq.com/s/CbKr4mev8qahKLAtV6Dxdg) - [MaxKB 应用案例:南京中医药大学](https://mp.weixin.qq.com/s/WUmAKYbZjp3272HIecpRFA) - [MaxKB 应用案例:西北电力设计院-AI数字助理Memex](https://mp.weixin.qq.com/s/ezHFdB7C7AVL9MTtDwYGSA) - [MaxKB 应用案例:西安国际医院中心医院-国医小助](https://mp.weixin.qq.com/s/DSOUvwrQrxbqQxKBilTCFQ) - [MaxKB 应用案例:华莱士智能AI客服助手上线啦!](https://www.bilibili.com/video/BV1hQtVeXEBL) - [MaxKB 应用案例:把医疗行业知识转化为知识库问答助手!](https://www.bilibili.com/video/BV157wme9EgB) - [MaxKB 应用案例:会展AI智能客服体验](https://www.bilibili.com/video/BV1J7BqY6EKA) - [MaxKB 应用案例:孩子要上幼儿园了,AI 智能助手择校好帮手](https://www.bilibili.com/video/BV1wKrhYvEer) - [MaxKB 应用案例:产品使用指南AI助手,新手小白也能轻松搞定!](https://www.bilibili.com/video/BV1Yz6gYtEqX) - [MaxKB 应用案例:生物医药AI客服智能体验!](https://www.bilibili.com/video/BV13JzvYsE3e) - [MaxKB 应用案例:高校行政管理AI小助手](https://www.bilibili.com/video/BV1yvBMYvEdy) - [MaxKB 应用案例:岳阳市人民医院-OA小助手](https://mp.weixin.qq.com/s/O94Qo3UH-MiUtDdWCVg8sQ) - [MaxKB 应用案例:常熟市第一人民医院](https://mp.weixin.qq.com/s/s5XXGTR3_MUo41NbJ8WzZQ) - [MaxKB 应用案例:华北水利水电大学](https://mp.weixin.qq.com/s/PoOFAcMCr9qJdvSj8c08qg) - [MaxKB 应用案例:唐山海事局-“小海”AI语音助手](https://news.qq.com/rain/a/20250223A030BE00) - [MaxKB 应用案例:湖南汉寿政务](http://hsds.hsdj.gov.cn:19999/ui/chat/a2c976736739aadc) - [MaxKB 应用案例:广州市妇女儿童医疗中心-AI医疗数据分类分级小助手](https://mp.weixin.qq.com/s/YHUMkUOAaUomBV8bswpK3g) - [MaxKB 应用案例:苏州热工研究院有限公司-维修大纲评估质量自查AI小助手](https://mp.weixin.qq.com/s/Ts5FQdnv7Tu9Jp7bvofCVA) - [MaxKB 应用案例:国核自仪系统工程有限公司-NuCON AI帮](https://mp.weixin.qq.com/s/HNPc7u5xVfGLJr8IQz3vjQ) - [MaxKB 应用案例:深圳通开启Deep Seek智能应用新篇章](https://mp.weixin.qq.com/s/SILN0GSescH9LyeQqYP0VQ) - [MaxKB 应用案例:南通智慧出行领跑长三角!首款接入DeepSeek的"畅行南通"APP上线AI新场景](https://mp.weixin.qq.com/s/WEC9UQ6msY0VS8LhTZh-Ew) - [MaxKB 应用案例:中船动力人工智能"智慧动力云助手"及首批数字员工正式上线](https://mp.weixin.qq.com/s/OGcEkjh9DzGO1Tkc9nr7qg) - [MaxKB 应用案例:AI+矿山:DeepSeek助力绿色智慧矿山智慧“升级”](https://mp.weixin.qq.com/s/SZstxTvVoLZg0ECbZbfpIA) - [MaxKB 应用案例:DeepSeek落地弘盛铜业:国产大模型点亮"黑灯工厂"新引擎](https://mp.weixin.qq.com/s/Eczdx574MS5RMF7WfHN7_A) - [MaxKB 应用案例:拥抱智能时代!中国五矿以 “AI+”赋能企业发展](https://mp.weixin.qq.com/s/D5vBtlX2E81pWE3_2OgWSw) - [MaxKB 应用案例:DeepSeek赋能中冶武勘AI智能体](https://mp.weixin.qq.com/s/8m0vxGcWXNdZazziQrLyxg) - [MaxKB 应用案例:重磅!陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA) - [MaxKB 应用案例:粤海集团完成DeepSeek私有化部署,助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg) - [MaxKB 应用案例:建筑材料工业信息中心完成DeepSeek本地化部署,推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw) - [MaxKB 应用案例:一起DeepSeek!福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA) ================================================ FILE: apps/__init__.py ================================================ ================================================ FILE: apps/application/__init__.py ================================================ ================================================ FILE: apps/application/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/application/api/application_access_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_access_token.py @date:2025/6/9 17:46 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_access_token import AccessTokenEditSerializer from common.mixins.api_mixin import APIMixin class ApplicationAccessTokenAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="应用id", type=OpenApiTypes.STR, location='path', required=True, )] @staticmethod def get_request(): return AccessTokenEditSerializer ================================================ FILE: apps/application/api/application_api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application.py @date:2025/5/26 16:59 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \ ApplicationImportRequest, ApplicationEditSerializer, TextToSpeechRequest, SpeechToTextRequest, PlayDemoTextRequest from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest): work_flow = serializers.DictField(required=True, label=_("Workflow Objects")) class ApplicationCreateResponse(ResultSerializer): def get_data(self): return ApplicationCreateSerializer.ApplicationResponse() class ApplicationListResult(ResultSerializer): def get_data(self): return ApplicationListResponse(many=True) class ApplicationPageResult(ResultPageSerializer): def get_data(self): return ApplicationListResponse(many=True) class ApplicationQueryAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="folder_id", description=_("folder id"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="name", description=_("Application Name"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="desc", description=_("Application Description"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="user_id", description=_("User ID"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="publish_status", description=_("Publish status") + '(published|unpublished)', type=OpenApiTypes.STR, location='query', required=False, ) ] @staticmethod def get_response(): return ApplicationListResult @staticmethod def get_page_response(): return ApplicationPageResult class ApplicationCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return ApplicationCreateRequest @staticmethod def get_response(): return ApplicationCreateResponse class ApplicationImportAPI(APIMixin): @staticmethod def get_parameters(): ApplicationCreateAPI.get_parameters() @staticmethod def get_request(): return ApplicationImportRequest class ApplicationOperateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="应用id", type=OpenApiTypes.STR, location='path', required=True, ) ] class ApplicationExportAPI(APIMixin): @staticmethod def get_parameters(): return ApplicationOperateAPI.get_parameters() @staticmethod def get_response(): return DefaultResultSerializer class ApplicationEditAPI(APIMixin): @staticmethod def get_request(): return ApplicationEditSerializer class TextToSpeechAPI(APIMixin): @staticmethod def get_parameters(): return ApplicationOperateAPI.get_parameters() @staticmethod def get_request(): return TextToSpeechRequest @staticmethod def get_response(): return DefaultResultSerializer class SpeechToTextAPI(APIMixin): @staticmethod def get_parameters(): return ApplicationOperateAPI.get_parameters() @staticmethod def get_request(): return SpeechToTextRequest @staticmethod def get_response(): return DefaultResultSerializer class PlayDemoTextAPI(APIMixin): @staticmethod def get_parameters(): return ApplicationOperateAPI.get_parameters() @staticmethod def get_request(): return PlayDemoTextRequest @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/application/api/application_api_key.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_api_key import EditApplicationKeySerializer, ApplicationKeySerializerModel from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer class ApplicationKeyListResult(ResultSerializer): def get_data(self): return ApplicationKeySerializerModel(many=True) class ApplicationKeyResult(ResultSerializer): def get_data(self): return ApplicationKeySerializerModel() class ApplicationKeyAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return ApplicationKeyResult class List(APIMixin): @staticmethod def get_response(): return ApplicationKeyListResult class Operate(APIMixin): @staticmethod def get_parameters(): return [*ApplicationKeyAPI.get_parameters(), OpenApiParameter( name="api_key_id", description="ApiKeyId", type=OpenApiTypes.STR, location='path', required=True, )] @staticmethod def get_request(): return EditApplicationKeySerializer ================================================ FILE: apps/application/api/application_chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat.py @date:2025/6/10 13:54 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_chat import ApplicationChatQuerySerializers, \ ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer class ApplicationChatListResponseSerializers(ResultSerializer): def get_data(self): return ApplicationChatResponseSerializers(many=True) class ApplicationChatPageResponseSerializers(ResultPageSerializer): def get_data(self): return ApplicationChatResponseSerializers(many=True) class ApplicationChatQueryAPI(APIMixin): @staticmethod def get_request(): return ApplicationChatQuerySerializers @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="start_time", description="start Time", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="end_time", description="end Time", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="abstract", description="summary", type=OpenApiTypes.STR, required=False, ), OpenApiParameter( name="username", description="username", type=OpenApiTypes.STR, required=False, ), OpenApiParameter( name="min_star", description=_("Minimum number of likes"), type=OpenApiTypes.INT, required=False, ), OpenApiParameter( name="min_trample", description=_("Minimum number of clicks"), type=OpenApiTypes.INT, required=False, ), OpenApiParameter( name="comparer", description=_("Comparator"), type=OpenApiTypes.STR, required=False, ), ] @staticmethod def get_response(): return ApplicationChatListResponseSerializers class ApplicationChatQueryPageAPI(APIMixin): @staticmethod def get_request(): return ApplicationChatQueryAPI.get_request() @staticmethod def get_parameters(): return [ *ApplicationChatQueryAPI.get_parameters(), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), ] @staticmethod def get_response(): return ApplicationChatPageResponseSerializers class ApplicationChatExportAPI(APIMixin): @staticmethod def get_request(): return ApplicationChatRecordExportRequest @staticmethod def get_parameters(): return ApplicationChatQueryAPI.get_parameters() @staticmethod def get_response(): return None ================================================ FILE: apps/application/api/application_chat_link.py ================================================ """ @project: MaxKB @Author: niu @file: application_chat_link.py @date: 2026/2/9 16:59 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from django.utils.translation import gettext_lazy as _ from application.serializers.application_chat_link import ChatRecordShareLinkRequestSerializer from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer class ChatRecordLinkAPI(APIMixin): @staticmethod def get_response(): return DefaultResultSerializer @staticmethod def get_request(): return ChatRecordShareLinkRequestSerializer @staticmethod def get_parameters(): return [ OpenApiParameter( name="application_id", description="Application ID", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="chat_id", description=_("Chat ID"), type=OpenApiTypes.STR, location='path', required=True, ), ] class ChatRecordDetailShareAPI(APIMixin): @staticmethod def get_response(): return DefaultResultSerializer @staticmethod def get_parameters(): return [ OpenApiParameter( name="link", description="链接", type=OpenApiTypes.STR, location='path', required=True, ) ] ================================================ FILE: apps/application/api/application_chat_record.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat_record.py @date:2025/6/10 15:19 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_chat_record import ApplicationChatRecordAddKnowledgeSerializer, \ ApplicationChatRecordImproveInstanceSerializer from common.mixins.api_mixin import APIMixin class ApplicationChatRecordQueryAPI(APIMixin): @staticmethod def get_response(): pass @staticmethod def get_request(): pass @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="Application ID", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="chat_id", description=_("Chat ID"), type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="order_asc", description=_("Is it in order"), type=OpenApiTypes.BOOL, required=True, ) ] class ApplicationChatRecordPageQueryAPI(APIMixin): @staticmethod def get_response(): pass @staticmethod def get_request(): pass @staticmethod def get_parameters(): return [*ApplicationChatRecordQueryAPI.get_parameters(), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, )] class ApplicationChatRecordImproveParagraphAPI(APIMixin): @staticmethod def get_response(): pass @staticmethod def get_request(): return ApplicationChatRecordImproveInstanceSerializer @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="Application ID", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="chat_id", description=_("Chat ID"), type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="chat_record_id", description=_("Chat Record ID"), type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description=_("Knowledge ID"), type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description=_("Document ID"), type=OpenApiTypes.STR, location='path', required=True, ) ] class Operate(APIMixin): @staticmethod def get_parameters(): return [*ApplicationChatRecordImproveParagraphAPI.get_parameters(), OpenApiParameter( name="paragraph_id", description=_("Paragraph ID"), type=OpenApiTypes.STR, location='path', required=True, )] class ApplicationChatRecordAddKnowledgeAPI(APIMixin): @staticmethod def get_request(): return ApplicationChatRecordAddKnowledgeSerializer @staticmethod def get_response(): return None @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="Application ID", type=OpenApiTypes.STR, location='path', required=True, )] ================================================ FILE: apps/application/api/application_stats.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_stats.py @date:2025/6/9 20:45 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_stats import ApplicationStatsSerializer from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer class ApplicationStatsResult(ResultSerializer): def get_data(self): return ApplicationStatsSerializer(many=True) class ApplicationStatsAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="start_time", description="start Time", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="end_time", description="end Time", type=OpenApiTypes.STR, required=True, ), ] @staticmethod def get_response(): return ApplicationStatsResult ================================================ FILE: apps/application/api/application_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py @date:2025/6/4 17:33 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_version import ApplicationVersionModelSerializer from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, PageDataResponse, ResultPageSerializer class ApplicationListVersionResult(ResultSerializer): def get_data(self): return ApplicationVersionModelSerializer(many=True) class ApplicationPageVersionResult(ResultPageSerializer): def get_data(self): return ApplicationVersionModelSerializer(many=True) class ApplicationWorkflowVersionResult(ResultSerializer): def get_data(self): return ApplicationVersionModelSerializer() class ApplicationVersionAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, location='path', required=True, ) ] class ApplicationVersionOperateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="application_version_id", description="工作流版本id", type=OpenApiTypes.STR, location='path', required=True, ) , *ApplicationVersionAPI.get_parameters() ] @staticmethod def get_response(): return ApplicationWorkflowVersionResult class ApplicationVersionListAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="name", description="Version Name", type=OpenApiTypes.STR, required=False, ) , *ApplicationVersionAPI.get_parameters()] @staticmethod def get_response(): return ApplicationListVersionResult class ApplicationVersionPageAPI(APIMixin): @staticmethod def get_parameters(): return ApplicationVersionListAPI.get_parameters() @staticmethod def get_response(): return ApplicationPageVersionResult ================================================ FILE: apps/application/apps.py ================================================ from django.apps import AppConfig class ApplicationConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'application' ================================================ FILE: apps/application/chat_pipeline/I_base_chat_pipeline.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: I_base_chat_pipeline.py @date:2024/1/9 17:25 @desc: """ import time from abc import abstractmethod from typing import Type import uuid_utils.compat as uuid from rest_framework import serializers from knowledge.models import Paragraph class ParagraphPipelineModel: def __init__(self, _id: str, document_id: str, knowledge_id: str, content: str, title: str, status: str, is_active: bool, comprehensive_score: float, similarity: float, knowledge_name: str, document_name: str, hit_handling_method: str, directly_return_similarity: float, knowledge_type, meta: dict = None): self.id = _id self.document_id = document_id self.knowledge_id = knowledge_id self.content = content self.title = title self.status = status, self.is_active = is_active self.comprehensive_score = comprehensive_score self.similarity = similarity self.knowledge_name = knowledge_name self.document_name = document_name self.hit_handling_method = hit_handling_method self.directly_return_similarity = directly_return_similarity self.meta = meta self.knowledge_type = knowledge_type def to_dict(self): return { 'id': self.id, 'document_id': self.document_id, 'knowledge_id': self.knowledge_id, 'content': self.content, 'title': self.title, 'status': self.status, 'is_active': self.is_active, 'comprehensive_score': self.comprehensive_score, 'similarity': self.similarity, 'knowledge_name': self.knowledge_name, 'document_name': self.document_name, 'knowledge_type': self.knowledge_type, 'meta': self.meta, } class builder: def __init__(self): self.similarity = None self.paragraph = {} self.comprehensive_score = None self.document_name = None self.knowledge_name = None self.knowledge_type = None self.hit_handling_method = None self.directly_return_similarity = 0.9 self.meta = {} def add_paragraph(self, paragraph): if isinstance(paragraph, Paragraph): self.paragraph = {'id': paragraph.id, 'document_id': paragraph.document_id, 'knowledge_id': paragraph.knowledge_id, 'content': paragraph.content, 'title': paragraph.title, 'status': paragraph.status, 'is_active': paragraph.is_active, } else: self.paragraph = paragraph return self def add_knowledge_name(self, knowledge_name): self.knowledge_name = knowledge_name return self def add_knowledge_type(self, knowledge_type): self.knowledge_type = knowledge_type return self def add_document_name(self, document_name): self.document_name = document_name return self def add_hit_handling_method(self, hit_handling_method): self.hit_handling_method = hit_handling_method return self def add_directly_return_similarity(self, directly_return_similarity): self.directly_return_similarity = directly_return_similarity return self def add_comprehensive_score(self, comprehensive_score: float): self.comprehensive_score = comprehensive_score return self def add_similarity(self, similarity: float): self.similarity = similarity return self def add_meta(self, meta: dict): self.meta = meta return self def build(self): return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')), str(self.paragraph.get('knowledge_id')), self.paragraph.get('content'), self.paragraph.get('title'), self.paragraph.get('status'), self.paragraph.get('is_active'), self.comprehensive_score, self.similarity, self.knowledge_name, self.document_name, self.hit_handling_method, self.directly_return_similarity, self.knowledge_type, self.meta) class IBaseChatPipelineStep: def __init__(self): # 当前步骤上下文,用于存储当前步骤信息 self.context = {} self.status = 200 self.err_message = '' @abstractmethod def get_step_serializer(self, manage) -> Type[serializers.Serializer]: pass def valid_args(self, manage): step_serializer_clazz = self.get_step_serializer(manage) step_serializer = step_serializer_clazz(data=manage.context) step_serializer.is_valid(raise_exception=True) self.context['step_args'] = step_serializer.data def run(self, manage): """ :param manage: 步骤管理器 :return: 执行结果 """ try: start_time = time.time() self.context['start_time'] = start_time # 校验参数, self.valid_args(manage) self._run(manage) self.context['run_time'] = time.time() - start_time except Exception as e: self.err_message = str(e) self.status = 500 chat_record_id = manage.context.get('chat_record_id') or str(uuid.uuid7()) manage.context['message_tokens'] = 0 manage.context['answer_tokens'] = 0 end_time = time.time() manage.context['run_time'] = end_time - (manage.context.get('start_time') or end_time) post_response_handler = manage.context.get('post_response_handler') post_response_handler.handler(manage.context.get('chat_id'), chat_record_id, manage.context.get('paragraph_list') or [], manage.context.get('problem_text'), str(e), manage, self, manage.context.get('padding_problem_text'), reasoning_content='') raise e def _run(self, manage): pass def execute(self, **kwargs): pass def get_details(self, manage, **kwargs): """ 运行详情 :return: 步骤详情 """ return None ================================================ FILE: apps/application/chat_pipeline/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 17:23 @desc: """ ================================================ FILE: apps/application/chat_pipeline/pipeline_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: pipeline_manage.py @date:2024/1/9 17:40 @desc: """ import time from functools import reduce from typing import List, Type, Dict from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse class PipelineManage: def __init__(self, step_list: List[Type[IBaseChatPipelineStep]], base_to_response: BaseToResponse = SystemToResponse(), debug=False): # 步骤执行器 self.step_list = [step() for step in step_list] self.run_step_list = [] # 上下文 self.context = {'message_tokens': 0, 'answer_tokens': 0} self.base_to_response = base_to_response self.debug = debug def run(self, context: Dict = None): self.context['start_time'] = time.time() if context is not None: for key, value in context.items(): self.context[key] = value for step in self.step_list: self.run_step_list.append(step) step.run(self) def get_details(self): return reduce(lambda x, y: {**x, **y}, [{item.get('step_type'): item} for item in filter(lambda r: r is not None, [row.get_details(self) for row in self.run_step_list])], {}) def get_base_to_response(self): return self.base_to_response class builder: def __init__(self): self.step_list: List[Type[IBaseChatPipelineStep]] = [] self.base_to_response = SystemToResponse() self.debug = False def append_step(self, step: Type[IBaseChatPipelineStep]): self.step_list.append(step) return self def add_base_to_response(self, base_to_response: BaseToResponse): self.base_to_response = base_to_response return self def add_debug(self, debug): self.debug = debug return self def build(self): return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response, debug=self.debug) ================================================ FILE: apps/application/chat_pipeline/step/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 18:23 @desc: """ ================================================ FILE: apps/application/chat_pipeline/step/chat_step/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 18:23 @desc: """ ================================================ FILE: apps/application/chat_pipeline/step/chat_step/i_chat_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_chat_step.py @date:2024/1/9 18:17 @desc: 对话 """ from abc import abstractmethod from typing import Type, List from django.utils.translation import gettext_lazy as _ from langchain.chat_models.base import BaseChatModel from langchain_core.messages import BaseMessage from rest_framework import serializers from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel from application.chat_pipeline.pipeline_manage import PipelineManage from application.serializers.application import NoReferencesSetting from common.field.common import InstanceField class ModelField(serializers.Field): def to_internal_value(self, data): if not isinstance(data, BaseChatModel): self.fail(_('Model type error'), value=data) return data def to_representation(self, value): return value class MessageField(serializers.Field): def to_internal_value(self, data): if not isinstance(data, BaseMessage): self.fail(_('Message type error'), value=data) return data def to_representation(self, value): return value class PostResponseHandler: @abstractmethod def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str, answer_text, manage, step, padding_problem_text: str = None, **kwargs): pass class IChatStep(IBaseChatPipelineStep): class InstanceSerializer(serializers.Serializer): # 对话列表 message_list = serializers.ListField(required=True, child=MessageField(required=True), label=_("Conversation list")) model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id")) # 段落列表 paragraph_list = serializers.ListField(label=_("Paragraph List")) # 对话id chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) # 用户问题 problem_text = serializers.CharField(required=True, label=_("User Questions")) # 后置处理器 post_response_handler = InstanceField(model_type=PostResponseHandler, label=_("Post-processor")) # 补全问题 padding_problem_text = serializers.CharField(required=False, label=_("Completion Question")) # 是否使用流的形式输出 stream = serializers.BooleanField(required=False, label=_("Streaming Output")) chat_user_id = serializers.CharField(required=True, label=_("Chat user id")) chat_record_id = serializers.CharField(required=False, label=_("Chat record id")) chat_user_type = serializers.CharField(required=True, label=_("Chat user Type")) # 未查询到引用分段 no_references_setting = NoReferencesSetting(required=True, label=_("No reference segment settings")) workspace_id = serializers.CharField(required=True, label=_("Workspace ID")) model_setting = serializers.DictField(required=True, allow_null=True, label=_("Model settings")) model_params_setting = serializers.DictField(required=False, allow_null=True, label=_("Model parameter settings")) mcp_tool_ids = serializers.JSONField(label="MCP工具ID列表", required=False, default=list) mcp_servers = serializers.JSONField(label="MCP服务列表", required=False, default=dict) mcp_source = serializers.CharField(label="MCP Source", required=False, default="referencing") tool_ids = serializers.JSONField(label="工具ID列表", required=False, default=list) application_ids = serializers.JSONField(label="应用ID列表", required=False, default=list) skill_tool_ids = serializers.JSONField(label="技能ID列表", required=False, default=list) mcp_output_enable = serializers.BooleanField(label="MCP输出是否启用", required=False, default=True) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) message_list: List = self.initial_data.get('message_list') for message in message_list: if not isinstance(message, BaseMessage): raise Exception(_("message type error")) def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: return self.InstanceSerializer def _run(self, manage: PipelineManage): chat_result = self.execute(**self.context['step_args'], manage=manage) manage.context['chat_result'] = chat_result @abstractmethod def execute(self, message_list: List[BaseMessage], chat_id, problem_text, post_response_handler: PostResponseHandler, model_id: str = None, workspace_id: str = None, paragraph_list=None, manage: PipelineManage = None, padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_type=None, no_references_setting=None, model_params_setting=None, model_setting=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, mcp_output_enable=True, **kwargs): pass ================================================ FILE: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_chat_step.py @date:2024/1/9 18:25 @desc: 对话step Base实现 """ import json import time import traceback from typing import List import uuid_utils.compat as uuid from django.db.models import QuerySet from django.http import StreamingHttpResponse from django.utils.translation import gettext as _ from langchain.chat_models.base import BaseChatModel from langchain_core.messages import AIMessageChunk, SystemMessage, BaseMessage, HumanMessage, AIMessage from rest_framework import status from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler from application.flow.tools import Reasoning, mcp_response_generator from application.models import ApplicationChatUserStats, ChatUserType, Application, ApplicationApiKey, \ ApplicationAccessToken from common.exception.app_exception import AppApiException from common.utils.logger import maxkb_logger from common.utils.rsa_util import rsa_long_decrypt from common.utils.shared_resource_auth import filter_authorized_ids from common.utils.tool_code import ToolExecutor from models_provider.tools import get_model_instance_by_model_workspace_id from tools.models import Tool def add_access_num(chat_user_id=None, chat_user_type=None, application_id=None): if [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__( chat_user_type) and application_id is not None: application_public_access_client = (QuerySet(ApplicationChatUserStats).filter(chat_user_id=chat_user_id, chat_user_type=chat_user_type, application_id=application_id) .first()) if application_public_access_client is not None: application_public_access_client.access_num = application_public_access_client.access_num + 1 application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1 application_public_access_client.save() def write_context(step, manage, request_token, response_token, all_text): step.context['message_tokens'] = request_token step.context['answer_tokens'] = response_token current_time = time.time() step.context['answer_text'] = all_text step.context['run_time'] = current_time - step.context['start_time'] manage.context['run_time'] = current_time - manage.context['start_time'] manage.context['message_tokens'] = manage.context['message_tokens'] + request_token manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token def event_content(response, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], post_response_handler: PostResponseHandler, manage, step, chat_model, message_list: List[BaseMessage], problem_text: str, padding_problem_text: str = None, chat_user_id=None, chat_user_type=None, is_ai_chat: bool = None, model_setting=None): if model_setting is None: model_setting = {} reasoning_content_enable = model_setting.get('reasoning_content_enable', False) reasoning_content_start = model_setting.get('reasoning_content_start', '') reasoning_content_end = model_setting.get('reasoning_content_end', '') reasoning = Reasoning(reasoning_content_start, reasoning_content_end) all_text = '' reasoning_content = '' try: response_reasoning_content = False for chunk in response: reasoning_chunk = reasoning.get_reasoning_content(chunk) content_chunk = reasoning_chunk.get('content') if 'reasoning_content' in chunk.additional_kwargs: response_reasoning_content = True reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') else: reasoning_content_chunk = reasoning_chunk.get('reasoning_content') content_chunk = reasoning._normalize_content(content_chunk) all_text += content_chunk if reasoning_content_chunk is None: reasoning_content_chunk = '' reasoning_content += reasoning_content_chunk yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', [], content_chunk, False, 0, 0, {'node_is_end': False, 'view_type': 'many_view', 'node_type': 'ai-chat-node', 'real_node_id': 'ai-chat-node', 'reasoning_content': reasoning_content_chunk if reasoning_content_enable else ''}) reasoning_chunk = reasoning.get_end_reasoning_content() all_text += reasoning_chunk.get('content') reasoning_content_chunk = "" if not response_reasoning_content: reasoning_content_chunk = reasoning_chunk.get( 'reasoning_content') yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', [], reasoning_chunk.get('content'), False, 0, 0, {'node_is_end': False, 'view_type': 'many_view', 'node_type': 'ai-chat-node', 'real_node_id': 'ai-chat-node', 'reasoning_content' : reasoning_content_chunk if reasoning_content_enable else ''}) # 获取token if is_ai_chat: try: request_token = chat_model.get_num_tokens_from_messages(message_list) response_token = chat_model.get_num_tokens(all_text) except Exception as e: request_token = 0 response_token = 0 else: request_token = 0 response_token = 0 write_context(step, manage, request_token, response_token, all_text) post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, all_text, manage, step, padding_problem_text, reasoning_content=reasoning_content if reasoning_content_enable else '') yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', [], '', True, request_token, response_token, {'node_is_end': True, 'view_type': 'many_view', 'node_type': 'ai-chat-node'}) if not manage.debug: add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id')) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') all_text = 'Exception:' + str(e) write_context(step, manage, 0, 0, all_text) post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, all_text, manage, step, padding_problem_text, reasoning_content='') if not manage.debug: add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id')) yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', [], all_text, False, 0, 0, {'node_is_end': False, 'view_type': 'many_view', 'node_type': 'ai-chat-node', 'real_node_id': 'ai-chat-node', 'reasoning_content': ''}) class BaseChatStep(IChatStep): def execute(self, message_list: List[BaseMessage], chat_id, problem_text, post_response_handler: PostResponseHandler, model_id: str = None, workspace_id: str = None, paragraph_list=None, manage: PipelineManage = None, padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_type=None, no_references_setting=None, model_params_setting=None, model_setting=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, mcp_output_enable=True, **kwargs): chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) if model_id is not None else None if stream: return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model, paragraph_list, manage, padding_problem_text, chat_user_id, chat_user_type, no_references_setting, model_setting, mcp_tool_ids, mcp_servers, mcp_source, tool_ids, application_ids, skill_tool_ids, workspace_id, mcp_output_enable) else: return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model, paragraph_list, manage, padding_problem_text, chat_user_id, chat_user_type, no_references_setting, model_setting, mcp_tool_ids, mcp_servers, mcp_source, tool_ids, application_ids, skill_tool_ids, workspace_id, mcp_output_enable) def get_details(self, manage, **kwargs): return { 'status': self.status, 'err_message': self.err_message, 'step_type': 'chat_step', 'run_time': self.context.get('run_time') or 0, 'model_id': str(manage.context['model_id']), 'message_list': self.reset_message_list(self.context['step_args'].get('message_list'), self.context.get('answer_text')), 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'cost': 0, } @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else ( 'system' if isinstance(message, SystemMessage) else 'ai'), 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids, application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, agent_id, chat_id): mcp_servers_config = {} # 迁移过来mcp_source是None if mcp_source is None: mcp_source = 'custom' # 兼容老数据 if not mcp_tool_ids: mcp_tool_ids = [] if mcp_source == 'custom' and mcp_servers and '"stdio"' not in mcp_servers: mcp_servers_config = json.loads(mcp_servers) elif mcp_tool_ids: mcp_tools = QuerySet(Tool).filter(id__in=mcp_tool_ids).values() for mcp_tool in mcp_tools: if mcp_tool and mcp_tool['is_active']: mcp_servers_config = {**mcp_servers_config, **json.loads(mcp_tool['code'])} tool_init_params = {} if tool_ids and len(tool_ids) > 0: # 如果有工具ID,则将其转换为MCP self.context['tool_ids'] = tool_ids for tool_id in tool_ids: tool = QuerySet(Tool).filter(id=tool_id).first() if tool is None or tool.is_active is False: continue executor = ToolExecutor() if tool.init_params is not None: params = json.loads(rsa_long_decrypt(tool.init_params)) tool_init_params = json.loads(rsa_long_decrypt(tool.init_params)) else: params = {} tool_config = executor.get_tool_mcp_config(tool, params) mcp_servers_config[str(tool.id)] = tool_config if application_ids and len(application_ids) > 0: self.context['application_ids'] = application_ids for application_id in application_ids: app = QuerySet(Application).filter(id=application_id, is_publish=True).first() if app is None: continue app_key = QuerySet(ApplicationApiKey).filter(application_id=application_id, is_active=True).first() if app_key is not None: api_key = app_key.secret_key application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=app_key.application_id ).first() if application_access_token is not None and application_access_token.authentication: raise AppApiException( 500, _('Agent 【{name}】 access token authentication is not supported for agent tool').format( name=app.name) ) else: raise AppApiException( 500, _('Agent Key is required for agent tool 【{name}】').format(name=app.name) ) executor = ToolExecutor() app_config = executor.get_app_mcp_config(api_key) mcp_servers_config[app.name] = app_config if skill_tool_ids and len(skill_tool_ids) > 0: self.context['skill_tool_ids'] = skill_tool_ids skill_file_items = [] for tool_id in skill_tool_ids: tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first() if tool is None or tool.is_active is False: continue init_params_default_value = {i["field"]: i.get('default_value') for i in tool.init_field_list} if tool.init_params is not None: params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params)) else: params = init_params_default_value skill_file_items.append({ 'tool_id': str(tool.id), 'file_id': tool.code, 'params': params }) mcp_servers_config['skills'] = skill_file_items if len(mcp_servers_config) > 0: source_id = agent_id source_type = 'APPLICATION' return mcp_response_generator( chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable, tool_init_params, source_id, source_type, chat_id ) return None def get_stream_result(self, message_list: List[BaseMessage], chat_model: BaseChatModel = None, paragraph_list=None, no_references_setting=None, problem_text=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, workspace_id=None, mcp_output_enable=True, agent_id=None, chat_id=None ): if paragraph_list is None: paragraph_list = [] directly_return_chunk_list = [AIMessageChunk(content=paragraph.content) for paragraph in paragraph_list if ( paragraph.hit_handling_method == 'directly_return' and paragraph.similarity >= paragraph.directly_return_similarity)] if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0: return iter(directly_return_chunk_list), False elif len(paragraph_list) == 0 and no_references_setting.get( 'status') == 'designated_answer': return iter( [AIMessageChunk(content=no_references_setting.get('value').replace('{question}', problem_text))]), False if chat_model is None: return iter([AIMessageChunk( _('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.'))]), False else: # 过滤tool_id all_tool_ids = list(set( (mcp_tool_ids or []) + (tool_ids or []) + (skill_tool_ids or []) )) authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id)) mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set] tool_ids = [i for i in (tool_ids or []) if i in authorized_set] skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set] # 处理 MCP 请求 mcp_result = self._handle_mcp_request( mcp_source, mcp_servers, mcp_tool_ids, tool_ids, application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, agent_id, chat_id ) if mcp_result: return mcp_result, True return chat_model.stream(message_list), True def execute_stream(self, message_list: List[BaseMessage], chat_id, problem_text, post_response_handler: PostResponseHandler, chat_model: BaseChatModel = None, paragraph_list=None, manage: PipelineManage = None, padding_problem_text: str = None, chat_user_id=None, chat_user_type=None, no_references_setting=None, model_setting=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, workspace_id=None, mcp_output_enable=True): chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list, no_references_setting, problem_text, mcp_tool_ids, mcp_servers, mcp_source, tool_ids, application_ids, skill_tool_ids, workspace_id, mcp_output_enable, manage.context.get('application_id'), chat_id) chat_record_id = self.context.get('step_args', {}).get('chat_record_id') if self.context.get('step_args', {}).get( 'chat_record_id') else uuid.uuid7() r = StreamingHttpResponse( streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list, post_response_handler, manage, self, chat_model, message_list, problem_text, padding_problem_text, chat_user_id, chat_user_type, is_ai_chat, model_setting), content_type='text/event-stream;charset=utf-8') r['Cache-Control'] = 'no-cache' return r def get_block_result(self, message_list: List[BaseMessage], chat_model: BaseChatModel = None, paragraph_list=None, no_references_setting=None, problem_text=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, workspace_id=None, mcp_output_enable=True, application_id=None, chat_id=None ): if paragraph_list is None: paragraph_list = [] directly_return_chunk_list = [AIMessageChunk(content=paragraph.content) for paragraph in paragraph_list if ( paragraph.hit_handling_method == 'directly_return' and paragraph.similarity >= paragraph.directly_return_similarity)] if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0: return directly_return_chunk_list[0], False elif len(paragraph_list) == 0 and no_references_setting.get( 'status') == 'designated_answer': return AIMessage(no_references_setting.get('value').replace('{question}', problem_text)), False if chat_model is None: return AIMessage( _('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.')), False else: # 过滤tool_id all_tool_ids = list(set( (mcp_tool_ids or []) + (tool_ids or []) + (skill_tool_ids or []) )) authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id)) mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set] tool_ids = [i for i in (tool_ids or []) if i in authorized_set] skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set] # 处理 MCP 请求 mcp_result = self._handle_mcp_request( mcp_source, mcp_servers, mcp_tool_ids, tool_ids, application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, application_id, chat_id ) if mcp_result: return mcp_result, True return chat_model.invoke(message_list), True def execute_block(self, message_list: List[BaseMessage], chat_id, problem_text, post_response_handler: PostResponseHandler, chat_model: BaseChatModel = None, paragraph_list=None, manage: PipelineManage = None, padding_problem_text: str = None, chat_user_id=None, chat_user_type=None, no_references_setting=None, model_setting=None, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing", tool_ids=None, application_ids=None, skill_tool_ids=None, workspace_id=None, mcp_output_enable=True): reasoning_content_enable = model_setting.get('reasoning_content_enable', False) reasoning_content_start = model_setting.get('reasoning_content_start', '') reasoning_content_end = model_setting.get('reasoning_content_end', '') reasoning = Reasoning(reasoning_content_start, reasoning_content_end) chat_record_id = uuid.uuid7() # 调用模型 try: chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list, no_references_setting, problem_text, mcp_tool_ids, mcp_servers, mcp_source, tool_ids, application_ids, skill_tool_ids,workspace_id, mcp_output_enable, manage.context.get('application_id'), chat_id) if is_ai_chat: request_token = chat_model.get_num_tokens_from_messages(message_list) response_token = chat_model.get_num_tokens(chat_result.content) else: request_token = 0 response_token = 0 write_context(self, manage, request_token, response_token, chat_result.content) reasoning_result = reasoning.get_reasoning_content(chat_result) reasoning_result_end = reasoning.get_end_reasoning_content() content = reasoning_result.get('content') + reasoning_result_end.get('content') if 'reasoning_content' in chat_result.response_metadata: reasoning_content = (chat_result.response_metadata.get('reasoning_content', '') or '') else: reasoning_content = (reasoning_result.get('reasoning_content') or "") + (reasoning_result_end.get( 'reasoning_content') or "") post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, content, manage, self, padding_problem_text, reasoning_content=reasoning_content) if not manage.debug: add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id')) return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), content, True, request_token, response_token, { 'reasoning_content': reasoning_content if reasoning_content_enable else '', 'answer_list': [{ 'content': content, 'reasoning_content': reasoning_content if reasoning_content_enable else '' }]}) except Exception as e: all_text = 'Exception:' + str(e) write_context(self, manage, 0, 0, all_text) post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, all_text, manage, self, padding_problem_text, reasoning_content='') if not manage.debug: add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id')) return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0, 0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR) ================================================ FILE: apps/application/chat_pipeline/step/generate_human_message_step/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 18:23 @desc: """ ================================================ FILE: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_generate_human_message_step.py @date:2024/1/9 18:15 @desc: 生成对话模板 """ from abc import abstractmethod from typing import Type, List from django.utils.translation import gettext_lazy as _ from langchain_core.messages import BaseMessage from rest_framework import serializers from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel from application.chat_pipeline.pipeline_manage import PipelineManage from application.models import ChatRecord from application.serializers.application import NoReferencesSetting from common.field.common import InstanceField class IGenerateHumanMessageStep(IBaseChatPipelineStep): class InstanceSerializer(serializers.Serializer): # 问题 problem_text = serializers.CharField(required=True, label=_("question")) # 段落列表 paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True), label=_("Paragraph List")) # 历史对答 history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), label=_("History Questions")) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) # 最大携带知识库段落长度 max_paragraph_char_number = serializers.IntegerField(required=True, label=_("Maximum length of the knowledge base paragraph")) # 模板 prompt = serializers.CharField(required=True, label=_("Prompt word")) system = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("System prompt words (role)")) # 补齐问题 padding_problem_text = serializers.CharField(required=False, label=_("Completion problem")) # 未查询到引用分段 no_references_setting = NoReferencesSetting(required=True, label=_("No reference segment settings")) def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: return self.InstanceSerializer def _run(self, manage: PipelineManage): message_list = self.execute(**self.context['step_args']) manage.context['message_list'] = message_list @abstractmethod def execute(self, problem_text: str, paragraph_list: List[ParagraphPipelineModel], history_chat_record: List[ChatRecord], dialogue_number: int, max_paragraph_char_number: int, prompt: str, padding_problem_text: str = None, no_references_setting=None, system=None, **kwargs) -> List[BaseMessage]: """ :param problem_text: 原始问题文本 :param paragraph_list: 段落列表 :param history_chat_record: 历史对话记录 :param dialogue_number: 多轮对话数量 :param max_paragraph_char_number: 最大段落长度 :param prompt: 模板 :param padding_problem_text 用户修改文本 :param kwargs: 其他参数 :param no_references_setting: 无引用分段设置 :param system 系统提示称 :return: """ pass ================================================ FILE: apps/application/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_generate_human_message_step.py.py @date:2024/1/10 17:50 @desc: """ from typing import List, Dict from langchain_core.messages import SystemMessage, BaseMessage, HumanMessage from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \ IGenerateHumanMessageStep from application.models import ChatRecord from common.utils.common import flat_map class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep): def execute(self, problem_text: str, paragraph_list: List[ParagraphPipelineModel], history_chat_record: List[ChatRecord], dialogue_number: int, max_paragraph_char_number: int, prompt: str, padding_problem_text: str = None, no_references_setting=None, system=None, **kwargs) -> List[BaseMessage]: prompt = prompt if (paragraph_list is not None and len(paragraph_list) > 0) else no_references_setting.get( 'value') exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text start_index = len(history_chat_record) - dialogue_number history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))] if system is not None and len(system) > 0: return [SystemMessage(system), *flat_map(history_message), self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list, no_references_setting)] return [*flat_map(history_message), self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list, no_references_setting)] @staticmethod def to_human_message(prompt: str, problem: str, max_paragraph_char_number: int, paragraph_list: List[ParagraphPipelineModel], no_references_setting: Dict): if paragraph_list is None or len(paragraph_list) == 0: if no_references_setting.get('status') == 'ai_questioning': return HumanMessage( content=no_references_setting.get('value').replace('{question}', problem)) else: return HumanMessage(content=prompt.replace('{data}', "").replace('{question}', problem)) temp_data = "" data_list = [] for p in paragraph_list: content = f"{p.title}:{p.content}" temp_data += content if len(temp_data) > max_paragraph_char_number: row_data = content[0:max_paragraph_char_number - len(temp_data)] data_list.append(f"{row_data}") break else: data_list.append(f"{content}") data = "\n".join(data_list) return HumanMessage(content=prompt.replace('{data}', data).replace('{question}', problem)) def get_details(self, manage, **kwargs): return { 'status': self.status, 'err_message': self.err_message, 'step_type': 'generate_human_message', } ================================================ FILE: apps/application/chat_pipeline/step/reset_problem_step/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 18:23 @desc: """ ================================================ FILE: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_reset_problem_step.py @date:2024/1/9 18:12 @desc: 重写处理问题 """ from abc import abstractmethod from typing import Type, List from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep from application.chat_pipeline.pipeline_manage import PipelineManage from application.models import ChatRecord from common.field.common import InstanceField class IResetProblemStep(IBaseChatPipelineStep): class InstanceSerializer(serializers.Serializer): # 问题文本 problem_text = serializers.CharField(required=True, label=_("question")) # 历史对答 history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), label=_("History Questions")) # 大语言模型 model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id")) workspace_id = serializers.CharField(required=True, label=_("User ID")) problem_optimization_prompt = serializers.CharField(required=False, max_length=102400, label=_("Question completion prompt")) def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: return self.InstanceSerializer def _run(self, manage: PipelineManage): padding_problem = self.execute(**self.context.get('step_args')) # 用户输入问题 source_problem_text = self.context.get('step_args').get('problem_text') self.context['problem_text'] = source_problem_text self.context['padding_problem_text'] = padding_problem manage.context['problem_text'] = source_problem_text manage.context['padding_problem_text'] = padding_problem # 累加tokens manage.context['message_tokens'] = manage.context.get('message_tokens', 0) + self.context.get('message_tokens', 0) manage.context['answer_tokens'] = manage.context.get('answer_tokens', 0) + self.context.get('answer_tokens', 0) @abstractmethod def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None, problem_optimization_prompt=None, workspace_id=None, **kwargs): pass ================================================ FILE: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_reset_problem_step.py @date:2024/1/10 14:35 @desc: """ from typing import List from django.utils.translation import gettext as _ from langchain_core.messages import HumanMessage from application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep from application.models import ChatRecord from common.utils.split_model import flat_map from models_provider.tools import get_model_instance_by_model_workspace_id prompt = _( "() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the tag") class BaseResetProblemStep(IResetProblemStep): def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None, problem_optimization_prompt=None, workspace_id=None, **kwargs) -> str: chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id) if model_id is not None else None if chat_model is None: return problem_text start_index = len(history_chat_record) - 3 history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))] reset_prompt = problem_optimization_prompt if problem_optimization_prompt else prompt message_list = [*flat_map(history_message), HumanMessage(content=reset_prompt.replace('{question}', problem_text))] response = chat_model.invoke(message_list) padding_problem = problem_text if response.content.__contains__("") and response.content.__contains__(''): padding_problem_data = response.content[ response.content.index('') + 6:response.content.index('')] if padding_problem_data is not None and len(padding_problem_data.strip()) > 0: padding_problem = padding_problem_data elif len(response.content) > 0: padding_problem = response.content try: request_token = chat_model.get_num_tokens_from_messages(message_list) response_token = chat_model.get_num_tokens(padding_problem) except Exception as e: request_token = 0 response_token = 0 self.context['message_tokens'] = request_token self.context['answer_tokens'] = response_token return padding_problem def get_details(self, manage, **kwargs): return {'status': self.status, 'err_message': self.err_message, 'step_type': 'problem_padding', 'run_time': self.context['run_time'], 'model_id': str(manage.context['model_id']) if 'model_id' in manage.context else None, 'message_tokens': self.context.get('message_tokens', 0), 'answer_tokens': self.context.get('answer_tokens', 0), 'cost': 0, 'padding_problem_text': self.context.get('padding_problem_text'), 'problem_text': self.context.get("step_args").get('problem_text'), } ================================================ FILE: apps/application/chat_pipeline/step/search_dataset_step/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/1/9 18:24 @desc: """ ================================================ FILE: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_search_dataset_step.py @date:2024/1/9 18:10 @desc: 检索知识库 """ import re from abc import abstractmethod from typing import List, Type from django.core import validators from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel from application.chat_pipeline.pipeline_manage import PipelineManage class ISearchDatasetStep(IBaseChatPipelineStep): class InstanceSerializer(serializers.Serializer): # 原始问题文本 problem_text = serializers.CharField(required=True, label=_("question")) # 系统补全问题文本 padding_problem_text = serializers.CharField(required=False, label=_("System completes question text")) # 需要查询的数据集id列表 knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_("Dataset id list")) # 需要排除的文档id exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_("List of document ids to exclude")) # 需要排除向量id exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_("List of exclusion vector ids")) # 需要查询的条数 top_n = serializers.IntegerField(required=True, label=_("Reference segment number")) # 相似度 0-1之间 similarity = serializers.FloatField(required=True, max_value=1, min_value=0, label=_("Similarity")) search_mode = serializers.CharField(required=True, validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_("The type only supports embedding|keywords|blend"), code=500) ], label=_("Retrieval Mode")) workspace_id = serializers.CharField(required=True, label=_("Workspace ID")) def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]: return self.InstanceSerializer def _run(self, manage: PipelineManage): paragraph_list = self.execute(**self.context['step_args'], manage=manage) manage.context['paragraph_list'] = paragraph_list self.context['paragraph_list'] = paragraph_list @abstractmethod def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str], exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None, search_mode: str = None, workspace_id=None, manage: PipelineManage = None, **kwargs) -> List[ParagraphPipelineModel]: """ 关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询 :param similarity: 相关性 :param top_n: 查询多少条 :param problem_text: 用户问题 :param knowledge_id_list: 需要查询的数据集id列表 :param exclude_document_id_list: 需要排除的文档id :param exclude_paragraph_id_list: 需要排除段落id :param padding_problem_text 补全问题 :param search_mode 检索模式 :param workspace_id 工作空间id :return: 段落列表 """ pass ================================================ FILE: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_search_dataset_step.py @date:2024/1/10 10:33 @desc: """ import os from typing import List, Dict from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework.utils.formatting import lazy_format from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep from common.config.embedding_config import VectorStore, ModelManage from common.constants.permission_constants import RoleConstants from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search from common.utils.common import get_file_content from knowledge.models import Paragraph, Knowledge from knowledge.models import SearchMode from maxkb.conf import PROJECT_DIR from models_provider.models import Model from models_provider.tools import get_model, get_model_by_id, get_model_default_params def reset_meta(meta): if not meta.get('allow_download', False): return {'allow_download': False} return meta def get_embedding_id(knowledge_id_list): knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list) if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1: raise Exception( _("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled.")) if len(knowledge_list) == 0: raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base")) return knowledge_list[0].embedding_model_id class BaseSearchDatasetStep(ISearchDatasetStep): def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str], exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None, search_mode: str = None, workspace_id=None, manage=None, **kwargs) -> List[ParagraphPipelineModel]: get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized') chat_user_type = manage.context.get('chat_user_type') if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type: knowledge_id_list = get_knowledge_list_of_authorized(manage.context.get('chat_user_id'), knowledge_id_list) if len(knowledge_id_list) == 0: return [] exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text model_id = get_embedding_id(knowledge_id_list) model = get_model_by_id(model_id, workspace_id) if model.model_type != "EMBEDDING": raise Exception(_("Model does not exist")) self.context['model_name'] = model.name default_params = get_model_default_params(model) embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model, **{**default_params})) embedding_value = embedding_model.embed_query(exec_problem_text) vector = VectorStore.get_embedding_vector() embedding_list = vector.query(exec_problem_text, embedding_value, knowledge_id_list, None, exclude_document_id_list, exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode)) if embedding_list is None: return [] paragraph_list = self.list_paragraph(embedding_list, vector) result = [self.reset_paragraph(paragraph, embedding_list) for paragraph in paragraph_list] return result @staticmethod def reset_paragraph(paragraph: Dict, embedding_list: List) -> ParagraphPipelineModel: filter_embedding_list = [embedding for embedding in embedding_list if str(embedding.get('paragraph_id')) == str(paragraph.get('id'))] if filter_embedding_list is not None and len(filter_embedding_list) > 0: find_embedding = filter_embedding_list[-1] return (ParagraphPipelineModel.builder() .add_paragraph(paragraph) .add_similarity(find_embedding.get('similarity')) .add_comprehensive_score(find_embedding.get('comprehensive_score')) .add_knowledge_name(paragraph.get('knowledge_name')) .add_knowledge_type(paragraph.get('knowledge_type')) .add_document_name(paragraph.get('document_name')) .add_hit_handling_method(paragraph.get('hit_handling_method')) .add_directly_return_similarity(paragraph.get('directly_return_similarity')) .add_meta(reset_meta(paragraph.get('meta'))) .build()) @staticmethod def get_similarity(paragraph, embedding_list: List): filter_embedding_list = [embedding for embedding in embedding_list if str(embedding.get('paragraph_id')) == str(paragraph.get('id'))] if filter_embedding_list is not None and len(filter_embedding_list) > 0: find_embedding = filter_embedding_list[-1] return find_embedding.get('comprehensive_score') return 0 @staticmethod def list_paragraph(embedding_list: List, vector): paragraph_id_list = [row.get('paragraph_id') for row in embedding_list] if paragraph_id_list is None or len(paragraph_id_list) == 0: return [] paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list), get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_knowledge_paragraph_by_paragraph_id.sql')), with_table_name=True) # 如果向量库中存在脏数据 直接删除 if len(paragraph_list) != len(paragraph_id_list): exist_paragraph_list = [row.get('id') for row in paragraph_list] for paragraph_id in paragraph_id_list: if not exist_paragraph_list.__contains__(paragraph_id): vector.delete_by_paragraph_id(paragraph_id) # 如果存在直接返回的则取直接返回段落 hit_handling_method_paragraph = [paragraph for paragraph in paragraph_list if (paragraph.get( 'hit_handling_method') == 'directly_return' and BaseSearchDatasetStep.get_similarity( paragraph, embedding_list) >= paragraph.get( 'directly_return_similarity'))] if len(hit_handling_method_paragraph) > 0: # 找到评分最高的 return [sorted(hit_handling_method_paragraph, key=lambda p: BaseSearchDatasetStep.get_similarity(p, embedding_list))[-1]] return paragraph_list def get_details(self, manage, **kwargs): step_args = self.context.get('step_args') or {} return { 'status': self.status, 'err_message': self.err_message, 'step_type': 'search_step', 'paragraph_list': [row.to_dict() for row in (self.context.get('paragraph_list') or [])], 'run_time': self.context.get('run_time') or 0, 'problem_text': step_args.get( 'padding_problem_text') if 'padding_problem_text' in step_args else step_args.get('problem_text'), 'model_name': self.context.get('model_name'), 'message_tokens': 0, 'answer_tokens': 0, 'cost': 0 } ================================================ FILE: apps/application/flow/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/6/7 14:43 @desc: """ ================================================ FILE: apps/application/flow/backend/__init__.py ================================================ ================================================ FILE: apps/application/flow/backend/sandbox_shell.py ================================================ import getpass import os import re from deepagents.backends import LocalShellBackend from deepagents.backends.protocol import ExecuteResponse from maxkb.const import CONFIG _enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0))) _run_user = 'sandbox' if _enable_sandbox else getpass.getuser() _sandbox_python_sys_path = CONFIG.get_sandbox_python_package_paths().replace(',', ':') class SandboxShellBackend(LocalShellBackend): def __init__(self, root_dir: str, **kwargs): if 'env' not in kwargs and not kwargs.get('inherit_env', False): env = os.environ.copy() path = env.get('PATH', '/usr/bin:/bin') # 将 sandbox 路径分解为列表,检查每个路径是否已存在 existing_paths = set(path.split(os.pathsep)) sandbox_paths = _sandbox_python_sys_path.split(os.pathsep) if _sandbox_python_sys_path else [] new_paths = [p for p in sandbox_paths if p and p not in existing_paths] if new_paths: env['PATH'] = f"{os.pathsep.join(new_paths)}{os.pathsep}{path}" kwargs['env'] = env super().__init__(root_dir=root_dir, **kwargs) def _translate_virtual_paths(self, command: str) -> str: """Translate virtual absolute paths in the command to real filesystem paths. In virtual_mode=True, file tools (ls, glob, read_file) return virtual absolute paths like /skills/foo.py which map to {root_dir}/skills/foo.py. But execute() runs a real shell where /skills/foo.py does not exist. This method replaces any path token that exists under root_dir with its real path, while leaving genuine system paths (e.g. /usr/bin/python3) untouched. """ root = str(self.cwd) def translate(m: re.Match) -> str: virtual_path = m.group(0) real_path = root + virtual_path return real_path if os.path.lexists(real_path) else virtual_path # Match absolute-path-like tokens: / followed by a non-whitespace sequence # that isn't clearly a flag (e.g. avoid matching -/something). # Only translate when virtual_mode is active. return re.sub(r'(?<:,]*', translate, command) def execute( self, command: str, *, timeout: int | None = None, ) -> ExecuteResponse: if self.virtual_mode: command = self._translate_virtual_paths(command) if _enable_sandbox: # 用 runuser 在子进程里切换用户,父进程凭据保持不变, # 避免父进程 ruid/euid 不一致导致 execve 报 Permission denied command = f"runuser -u {_run_user} -- env -i LD_PRELOAD=/opt/maxkb-app/sandbox/lib/sandbox.so PATH=${{PATH}} {command}" # command = f"runuser -u {_run_user} -- env -i PATH=${{PATH}} {command}" # print(f"Executing command in sandbox: {command}") return super().execute(command=command, timeout=timeout) ================================================ FILE: apps/application/flow/common.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: common.py @date:2024/12/11 17:57 @desc: """ from enum import Enum from typing import List, Dict from django.db.models import QuerySet from django.utils.translation import gettext as _ from rest_framework.exceptions import ErrorDetail, ValidationError from common.exception.app_exception import AppApiException from common.utils.common import group_by from models_provider.models import Model from models_provider.tools import get_model_credential from tools.models.tool import Tool end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node', 'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node', 'variable-assign-node'] class Answer: def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node, real_node_id, reasoning_content): self.view_type = view_type self.content = content self.reasoning_content = reasoning_content self.runtime_node_id = runtime_node_id self.chat_record_id = chat_record_id self.child_node = child_node self.real_node_id = real_node_id def to_dict(self): return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id, 'chat_record_id': self.chat_record_id, 'child_node': self.child_node, 'reasoning_content': self.reasoning_content, 'real_node_id': self.real_node_id} class NodeChunk: def __init__(self): self.status = 0 self.chunk_list = [] def add_chunk(self, chunk): self.chunk_list.append(chunk) def end(self, chunk=None): if chunk is not None: self.add_chunk(chunk) self.status = 200 def is_end(self): return self.status == 200 class Edge: def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords): self.id = _id self.type = _type self.sourceNodeId = sourceNodeId self.targetNodeId = targetNodeId for keyword in keywords: self.__setattr__(keyword, keywords.get(keyword)) class Node: def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs): self.id = _id self.type = _type self.x = x self.y = y self.properties = properties for keyword in kwargs: self.__setattr__(keyword, kwargs.get(keyword)) class EdgeNode: edge: Edge node: Node def __init__(self, edge, node): self.edge = edge self.node = node class WorkflowMode(Enum): APPLICATION = "application" APPLICATION_LOOP = "application-loop" KNOWLEDGE = "knowledge" KNOWLEDGE_LOOP = "knowledge-loop" TOOL = "tool" TOOL_LOOP = "tool-loop" class Workflow: """ 节点列表 """ nodes: List[Node] """ 线列表 """ edges: List[Edge] """ 节点id:node """ node_map: Dict[str, Node] """ 节点id:当前节点id上面的所有节点 """ up_node_map: Dict[str, List[EdgeNode]] """ 节点id:当前节点id下面的所有节点 """ next_node_map: Dict[str, List[EdgeNode]] workflow_mode: WorkflowMode def __init__(self, nodes: List[Node], edges: List[Edge], workflow_mode: WorkflowMode = WorkflowMode.APPLICATION.value): self.nodes = nodes self.edges = edges self.node_map = {node.id: node for node in nodes} self.up_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.sourceNodeId)) for edge in edges] for key, edges in group_by(edges, key=lambda edge: edge.targetNodeId).items()} self.next_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.targetNodeId)) for edge in edges] for key, edges in group_by(edges, key=lambda edge: edge.sourceNodeId).items()} self.workflow_mode = workflow_mode def get_node(self, node_id): """ 根据node_id 获取节点信息 @param node_id: node_id @return: 节点信息 """ return self.node_map.get(node_id) def get_up_edge_nodes(self, node_id) -> List[EdgeNode]: """ 根据节点id 获取当前连接前置节点和连线 @param node_id: 节点id @return: 节点连线列表 """ return self.up_node_map.get(node_id) def get_next_edge_nodes(self, node_id) -> List[EdgeNode]: """ 根据节点id 获取当前连接目标节点和连线 @param node_id: 节点id @return: 节点连线列表 """ return self.next_node_map.get(node_id) def get_up_nodes(self, node_id) -> List[Node]: """ 根据节点id 获取当前连接前置节点 @param node_id: 节点id @return: 节点列表 """ return [en.node for en in (self.up_node_map.get(node_id) or [])] def get_next_nodes(self, node_id) -> List[Node]: """ 根据节点id 获取当前连接目标节点 @param node_id: 节点id @return: 节点列表 """ return [en.node for en in self.next_node_map.get(node_id, [])] @staticmethod def new_instance(flow_obj: Dict, workflow_mode: WorkflowMode = WorkflowMode.APPLICATION): nodes = flow_obj.get('nodes') edges = flow_obj.get('edges') nodes = [Node(node.get('id'), node.get('type'), **node) for node in nodes] edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges] return Workflow(nodes, edges, workflow_mode) def get_start_node(self): return self.get_node('start-node') def get_search_node(self): return [node for node in self.nodes if node.type == 'search-dataset-node'] def is_valid(self): """ 校验工作流数据 """ self.is_valid_model_params() self.is_valid_start_node() self.is_valid_base_node() self.is_valid_work_flow() def is_valid_node_params(self, node: Node): from application.flow.step_node import get_node get_node(node.type, self.workflow_mode)(node, None, None) def is_valid_node(self, node: Node): self.is_valid_node_params(node) if node.type == 'condition-node': branch_list = node.properties.get('node_data').get('branch') for branch in branch_list: source_anchor_id = f"{node.id}_{branch.get('id')}_right" edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id] if len(edge_list) == 0: raise AppApiException(500, _('The branch {branch} of the {node} node needs to be connected').format( node=node.properties.get("stepName"), branch=branch.get("type"))) else: edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id] if len(edge_list) == 0 and not end_nodes.__contains__(node.type): raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format( node=node.properties.get("stepName"))) def is_valid_work_flow(self, up_node=None): if up_node is None: up_node = self.get_start_node() self.is_valid_node(up_node) next_nodes = self.get_next_nodes(up_node) for next_node in next_nodes: self.is_valid_work_flow(next_node) def is_valid_start_node(self): start_node_list = [node for node in self.nodes if node.id == 'start-node'] if len(start_node_list) == 0: raise AppApiException(500, _('The starting node is required')) if len(start_node_list) > 1: raise AppApiException(500, _('There can only be one starting node')) def is_valid_model_params(self): node_list = [node for node in self.nodes if ( node.type == 'ai-chat-node' or node.type == 'question-node' or node.type == 'parameter-extraction-node')] for node in node_list: model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first() if model is None: raise ValidationError(ErrorDetail( _('The node {node} model does not exist').format(node=node.properties.get("stepName")))) credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = node.properties.get('node_data', {}).get('model_params_setting') model_params_setting_form = credential.get_model_params_setting_form( model.model_name) if model_params_setting is None: model_params_setting = model_params_setting_form.get_default_form_data() node.properties.get('node_data', {})['model_params_setting'] = model_params_setting if node.properties.get('status', 200) != 200: raise ValidationError( ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName")))) node_list = [node for node in self.nodes if (node.type == 'function-lib-node')] for node in node_list: function_lib_id = node.properties.get('node_data', {}).get('function_lib_id') if function_lib_id is None: raise ValidationError(ErrorDetail( _('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName")))) f_lib = QuerySet(Tool).filter(id=function_lib_id).first() if f_lib is None: raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format( node=node.properties.get("stepName")))) def is_valid_base_node(self): base_node_list = [node for node in self.nodes if node.id == 'base-node'] if len(base_node_list) == 0: raise AppApiException(500, _('Basic information node is required')) if len(base_node_list) > 1: raise AppApiException(500, _('There can only be one basic information node')) ================================================ FILE: apps/application/flow/compare/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/6/7 14:43 @desc: """ from .contain_compare import * from .end_with import EndWithCompare from .equal_compare import * from .ge_compare import * from .gt_compare import * from .is_not_null_compare import * from .is_not_true import IsNotTrueCompare from .is_null_compare import * from .is_true import IsTrueCompare from .le_compare import * from .len_equal_compare import * from .len_ge_compare import * from .len_gt_compare import * from .len_le_compare import * from .len_lt_compare import * from .lt_compare import * from .not_contain_compare import * from .not_equal_compare import * from .start_with import StartWithCompare compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(), LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(), IsNullCompare(), IsNotNullCompare(), NotContainCompare(), NotEqualCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(), EndWithCompare()] ================================================ FILE: apps/application/flow/compare/compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: compare.py @date:2024/6/7 14:37 @desc: """ from abc import abstractmethod from typing import List class Compare: @abstractmethod def support(self, node_id, fields: List[str], source_value, compare, target_value): pass @abstractmethod def compare(self, source_value, compare, target_value): pass ================================================ FILE: apps/application/flow/compare/contain_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: contain_compare.py @date:2024/6/11 10:02 @desc: """ from typing import List from application.flow.compare.compare import Compare class ContainCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'contain': return True def compare(self, source_value, compare, target_value): if isinstance(source_value, str): return str(target_value) in source_value elif isinstance(source_value, list): return any([str(item) == str(target_value) for item in source_value]) else: return str(target_value) in str(source_value) ================================================ FILE: apps/application/flow/compare/end_with.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: start_with.py @date:2025/10/20 10:37 @desc: """ from typing import List from application.flow.compare import Compare class EndWithCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'end_with': return True def compare(self, source_value, compare, target_value): source_value = str(source_value) return source_value.endswith(str(target_value)) ================================================ FILE: apps/application/flow/compare/equal_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: equal_compare.py @date:2024/6/7 14:44 @desc: """ from typing import List from application.flow.compare import Compare class EqualCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'eq': return True def compare(self, source_value, compare, target_value): return str(source_value) == str(target_value) ================================================ FILE: apps/application/flow/compare/ge_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 大于比较器 """ from typing import List from application.flow.compare import Compare class GECompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'ge': return True def compare(self, source_value, compare, target_value): try: return float(source_value) >= float(target_value) except Exception as e: try: return str(source_value) >= str(target_value) except Exception as _: pass return False ================================================ FILE: apps/application/flow/compare/gt_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 大于比较器 """ from typing import List from application.flow.compare import Compare class GTCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'gt': return True def compare(self, source_value, compare, target_value): try: return float(source_value) > float(target_value) except Exception as e: try: return str(source_value) > str(target_value) except Exception as _: pass return False ================================================ FILE: apps/application/flow/compare/is_not_null_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: is_not_null_compare.py @date:2024/6/28 10:45 @desc: """ from typing import List from application.flow.compare import Compare class IsNotNullCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'is_not_null': return True def compare(self, source_value, compare, target_value): return source_value is not None and len(source_value) > 0 ================================================ FILE: apps/application/flow/compare/is_not_true.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: is_not_true.py @date:2025/4/7 13:44 @desc: """ from typing import List from application.flow.compare import Compare class IsNotTrueCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'is_not_true': return True def compare(self, source_value, compare, target_value): try: return source_value is False except Exception as e: return False ================================================ FILE: apps/application/flow/compare/is_null_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: is_null_compare.py @date:2024/6/28 10:45 @desc: """ from typing import List from application.flow.compare import Compare class IsNullCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'is_null': return True def compare(self, source_value, compare, target_value): try: return source_value is None or len(source_value) == 0 except Exception as e: return False ================================================ FILE: apps/application/flow/compare/is_true.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: IsTrue.py @date:2025/4/7 13:38 @desc: """ from typing import List from application.flow.compare import Compare class IsTrueCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'is_true': return True def compare(self, source_value, compare, target_value): try: return source_value is True except Exception as e: return False ================================================ FILE: apps/application/flow/compare/le_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 小于比较器 """ from typing import List from application.flow.compare import Compare class LECompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'le': return True def compare(self, source_value, compare, target_value): try: return float(source_value) <= float(target_value) except Exception as e: try: return str(source_value) <= str(target_value) except Exception as _: pass return False ================================================ FILE: apps/application/flow/compare/len_equal_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: equal_compare.py @date:2024/6/7 14:44 @desc: """ from typing import List from application.flow.compare import Compare class LenEqualCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'len_eq': return True def compare(self, source_value, compare, target_value): try: return len(source_value) == int(target_value) except Exception as e: return False ================================================ FILE: apps/application/flow/compare/len_ge_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 大于比较器 """ from typing import List from application.flow.compare import Compare class LenGECompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'len_ge': return True def compare(self, source_value, compare, target_value): try: return len(source_value) >= int(target_value) except Exception as e: return False ================================================ FILE: apps/application/flow/compare/len_gt_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 大于比较器 """ from typing import List from application.flow.compare import Compare class LenGTCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'len_gt': return True def compare(self, source_value, compare, target_value): try: return len(source_value) > int(target_value) except Exception as e: return False ================================================ FILE: apps/application/flow/compare/len_le_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 小于比较器 """ from typing import List from application.flow.compare import Compare class LenLECompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'len_le': return True def compare(self, source_value, compare, target_value): try: return len(source_value) <= int(target_value) except Exception as e: return False ================================================ FILE: apps/application/flow/compare/len_lt_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 小于比较器 """ from typing import List from application.flow.compare import Compare class LenLTCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'len_lt': return True def compare(self, source_value, compare, target_value): try: return len(source_value) < int(target_value) except Exception as e: return False ================================================ FILE: apps/application/flow/compare/lt_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: lt_compare.py @date:2024/6/11 9:52 @desc: 小于比较器 """ from typing import List from application.flow.compare import Compare class LTCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'lt': return True def compare(self, source_value, compare, target_value): try: return float(source_value) < float(target_value) except Exception as e: try: return str(source_value) < str(target_value) except Exception as _: pass return False ================================================ FILE: apps/application/flow/compare/not_contain_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: contain_compare.py @date:2024/6/11 10:02 @desc: """ from typing import List from application.flow.compare import Compare class NotContainCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'not_contain': return True def compare(self, source_value, compare, target_value): if isinstance(source_value, str): return str(target_value) not in source_value elif isinstance(self, list): return not any([str(item) == str(target_value) for item in source_value]) else: return str(target_value) not in str(source_value) ================================================ FILE: apps/application/flow/compare/not_equal_compare.py ================================================ # coding=utf-8 """ @project: maxkb @Author:wangliang181230 @file: not_equal_compare.py @date:2026/3/17 9:41 @desc: """ from typing import List from application.flow.compare import Compare class NotEqualCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'not_eq': return True def compare(self, source_value, compare, target_value): return str(source_value) != str(target_value) ================================================ FILE: apps/application/flow/compare/start_with.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: start_with.py @date:2025/10/20 10:37 @desc: """ from typing import List from application.flow.compare import Compare class StartWithCompare(Compare): def support(self, node_id, fields: List[str], source_value, compare, target_value): if compare == 'start_with': return True def compare(self, source_value, compare, target_value): source_value = str(source_value) return source_value.startswith(str(target_value)) ================================================ FILE: apps/application/flow/default_workflow.json ================================================ { "nodes": [ { "id": "base-node", "type": "base-node", "x": 360, "y": 2810, "properties": { "config": { }, "height": 825.6, "stepName": "基本信息", "node_data": { "desc": "", "name": "maxkbapplication", "prologue": "您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?" }, "input_field_list": [ ] } }, { "id": "start-node", "type": "start-node", "x": 430, "y": 3660, "properties": { "config": { "fields": [ { "label": "用户问题", "value": "question" } ], "globalFields": [ { "label": "当前时间", "value": "time" } ] }, "fields": [ { "label": "用户问题", "value": "question" } ], "height": 276, "stepName": "开始", "globalFields": [ { "label": "当前时间", "value": "time" } ] } }, { "id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "type": "search-dataset-node", "x": 840, "y": 3210, "properties": { "config": { "fields": [ { "label": "检索结果的分段列表", "value": "paragraph_list" }, { "label": "满足直接回答的分段列表", "value": "is_hit_handling_method_list" }, { "label": "检索结果", "value": "data" }, { "label": "满足直接回答的分段内容", "value": "directly_return" } ] }, "height": 794, "stepName": "知识库检索", "node_data": { "dataset_id_list": [ ], "dataset_setting": { "top_n": 3, "similarity": 0.6, "search_mode": "embedding", "max_paragraph_char_number": 5000 }, "question_reference_address": [ "start-node", "question" ], "source_dataset_id_list": [ ] } } }, { "id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "type": "condition-node", "x": 1490, "y": 3210, "properties": { "width": 600, "config": { "fields": [ { "label": "分支名称", "value": "branch_name" } ] }, "height": 543.675, "stepName": "判断器", "node_data": { "branch": [ { "id": "1009", "type": "IF", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "is_hit_handling_method_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "4908", "type": "ELSE IF 1", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "paragraph_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "161", "type": "ELSE", "condition": "and", "conditions": [ ] } ] }, "branch_condition_list": [ { "index": 0, "height": 121.225, "id": "1009" }, { "index": 1, "height": 121.225, "id": "4908" }, { "index": 2, "height": 44, "id": "161" } ] } }, { "id": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "type": "reply-node", "x": 2170, "y": 2480, "properties": { "config": { "fields": [ { "label": "内容", "value": "answer" } ] }, "height": 378, "stepName": "指定回复", "node_data": { "fields": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "directly_return" ], "content": "", "reply_type": "referencing", "is_result": true } } }, { "id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "type": "ai-chat-node", "x": 2160, "y": 3200, "properties": { "config": { "fields": [ { "label": "AI 回答内容", "value": "answer" } ] }, "height": 763, "stepName": "AI 对话", "node_data": { "prompt": "已知信息:\n{{知识库检索.data}}\n问题:\n{{开始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } }, { "id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "type": "ai-chat-node", "x": 2160, "y": 3970, "properties": { "config": { "fields": [ { "label": "AI 回答内容", "value": "answer" } ] }, "height": 763, "stepName": "AI 对话1", "node_data": { "prompt": "{{开始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } } ], "edges": [ { "id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73", "type": "app-edge", "sourceNodeId": "start-node", "targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "startPoint": { "x": 590, "y": 3660 }, "endPoint": { "x": 680, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 590, "y": 3660 }, { "x": 700, "y": 3660 }, { "x": 570, "y": 3210 }, { "x": 680, "y": 3210 } ], "sourceAnchorId": "start-node_right", "targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left" }, { "id": "35cb86dd-f328-429e-a973-12fd7218b696", "type": "app-edge", "sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "startPoint": { "x": 1000, "y": 3210 }, "endPoint": { "x": 1200, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 1000, "y": 3210 }, { "x": 1110, "y": 3210 }, { "x": 1090, "y": 3210 }, { "x": 1200, "y": 3210 } ], "sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right", "targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left" }, { "id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "startPoint": { "x": 1780, "y": 3073.775 }, "endPoint": { "x": 2010, "y": 2480 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3073.775 }, { "x": 1890, "y": 3073.775 }, { "x": 1900, "y": 2480 }, { "x": 2010, "y": 2480 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right", "targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left" }, { "id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "startPoint": { "x": 1780, "y": 3203 }, "endPoint": { "x": 2000, "y": 3200 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3203 }, { "x": 1890, "y": 3203 }, { "x": 1890, "y": 3200 }, { "x": 2000, "y": 3200 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right", "targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left" }, { "id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "startPoint": { "x": 1780, "y": 3293.6124999999997 }, "endPoint": { "x": 2000, "y": 3970 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3293.6124999999997 }, { "x": 1890, "y": 3293.6124999999997 }, { "x": 1890, "y": 3970 }, { "x": 2000, "y": 3970 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right", "targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left" } ] } ================================================ FILE: apps/application/flow/default_workflow_en.json ================================================ { "nodes": [ { "id": "base-node", "type": "base-node", "x": 360, "y": 2810, "properties": { "config": { }, "height": 825.6, "stepName": "Base", "node_data": { "desc": "", "name": "maxkbapplication", "prologue": "Hello, I am the MaxKB assistant. You can ask me about MaxKB usage issues.\n-What are the main functions of MaxKB?\n-What major language models does MaxKB support?\n-What document types does MaxKB support?" }, "input_field_list": [ ] } }, { "id": "start-node", "type": "start-node", "x": 430, "y": 3660, "properties": { "config": { "fields": [ { "label": "User Question", "value": "question" } ], "globalFields": [ { "label": "Current Time", "value": "time" } ] }, "fields": [ { "label": "User Question", "value": "question" } ], "height": 276, "stepName": "Start", "globalFields": [ { "label": "Current Time", "value": "time" } ] } }, { "id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "type": "search-dataset-node", "x": 840, "y": 3210, "properties": { "config": { "fields": [ { "label": "List of Retrieved Paragraphs", "value": "paragraph_list" }, { "label": "List of Paragraphs Satisfying Direct Answer", "value": "is_hit_handling_method_list" }, { "label": "Search Results", "value": "data" }, { "label": "Content of Paragraphs Satisfying Direct Answer", "value": "directly_return" } ] }, "height": 794, "stepName": "Knowledge Search", "node_data": { "dataset_id_list": [ ], "dataset_setting": { "top_n": 3, "similarity": 0.6, "search_mode": "embedding", "max_paragraph_char_number": 5000 }, "question_reference_address": [ "start-node", "question" ], "source_dataset_id_list": [ ] } } }, { "id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "type": "condition-node", "x": 1490, "y": 3210, "properties": { "width": 600, "config": { "fields": [ { "label": "Branch Name", "value": "branch_name" } ] }, "height": 543.675, "stepName": "Conditional Branch", "node_data": { "branch": [ { "id": "1009", "type": "IF", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "is_hit_handling_method_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "4908", "type": "ELSE IF 1", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "paragraph_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "161", "type": "ELSE", "condition": "and", "conditions": [ ] } ] }, "branch_condition_list": [ { "index": 0, "height": 121.225, "id": "1009" }, { "index": 1, "height": 121.225, "id": "4908" }, { "index": 2, "height": 44, "id": "161" } ] } }, { "id": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "type": "reply-node", "x": 2170, "y": 2480, "properties": { "config": { "fields": [ { "label": "Content", "value": "answer" } ] }, "height": 378, "stepName": "Specified Reply", "node_data": { "fields": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "directly_return" ], "content": "", "reply_type": "referencing", "is_result": true } } }, { "id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "type": "ai-chat-node", "x": 2160, "y": 3200, "properties": { "config": { "fields": [ { "label": "AI Answer Content", "value": "answer" } ] }, "height": 763, "stepName": "AI Chat", "node_data": { "prompt": "Known information:\n{{Knowledge Search.data}}\nQuestion:\n{{Start.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } }, { "id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "type": "ai-chat-node", "x": 2160, "y": 3970, "properties": { "config": { "fields": [ { "label": "AI Answer Content", "value": "answer" } ] }, "height": 763, "stepName": "AI Chat1", "node_data": { "prompt": "{{Start.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } } ], "edges": [ { "id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73", "type": "app-edge", "sourceNodeId": "start-node", "targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "startPoint": { "x": 590, "y": 3660 }, "endPoint": { "x": 680, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 590, "y": 3660 }, { "x": 700, "y": 3660 }, { "x": 570, "y": 3210 }, { "x": 680, "y": 3210 } ], "sourceAnchorId": "start-node_right", "targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left" }, { "id": "35cb86dd-f328-429e-a973-12fd7218b696", "type": "app-edge", "sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "startPoint": { "x": 1000, "y": 3210 }, "endPoint": { "x": 1200, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 1000, "y": 3210 }, { "x": 1110, "y": 3210 }, { "x": 1090, "y": 3210 }, { "x": 1200, "y": 3210 } ], "sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right", "targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left" }, { "id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "startPoint": { "x": 1780, "y": 3073.775 }, "endPoint": { "x": 2010, "y": 2480 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3073.775 }, { "x": 1890, "y": 3073.775 }, { "x": 1900, "y": 2480 }, { "x": 2010, "y": 2480 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right", "targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left" }, { "id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "startPoint": { "x": 1780, "y": 3203 }, "endPoint": { "x": 2000, "y": 3200 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3203 }, { "x": 1890, "y": 3203 }, { "x": 1890, "y": 3200 }, { "x": 2000, "y": 3200 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right", "targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left" }, { "id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "startPoint": { "x": 1780, "y": 3293.6124999999997 }, "endPoint": { "x": 2000, "y": 3970 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3293.6124999999997 }, { "x": 1890, "y": 3293.6124999999997 }, { "x": 1890, "y": 3970 }, { "x": 2000, "y": 3970 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right", "targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left" } ] } ================================================ FILE: apps/application/flow/default_workflow_zh.json ================================================ { "nodes": [ { "id": "base-node", "type": "base-node", "x": 360, "y": 2810, "properties": { "config": { }, "height": 825.6, "stepName": "基本信息", "node_data": { "desc": "", "name": "maxkbapplication", "prologue": "您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?" }, "input_field_list": [ ] } }, { "id": "start-node", "type": "start-node", "x": 430, "y": 3660, "properties": { "config": { "fields": [ { "label": "用户问题", "value": "question" } ], "globalFields": [ { "label": "当前时间", "value": "time" } ] }, "fields": [ { "label": "用户问题", "value": "question" } ], "height": 276, "stepName": "开始", "globalFields": [ { "label": "当前时间", "value": "time" } ] } }, { "id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "type": "search-dataset-node", "x": 840, "y": 3210, "properties": { "config": { "fields": [ { "label": "检索结果的分段列表", "value": "paragraph_list" }, { "label": "满足直接回答的分段列表", "value": "is_hit_handling_method_list" }, { "label": "检索结果", "value": "data" }, { "label": "满足直接回答的分段内容", "value": "directly_return" } ] }, "height": 794, "stepName": "知识库检索", "node_data": { "dataset_id_list": [ ], "dataset_setting": { "top_n": 3, "similarity": 0.6, "search_mode": "embedding", "max_paragraph_char_number": 5000 }, "question_reference_address": [ "start-node", "question" ], "source_dataset_id_list": [ ] } } }, { "id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "type": "condition-node", "x": 1490, "y": 3210, "properties": { "width": 600, "config": { "fields": [ { "label": "分支名称", "value": "branch_name" } ] }, "height": 543.675, "stepName": "判断器", "node_data": { "branch": [ { "id": "1009", "type": "IF", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "is_hit_handling_method_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "4908", "type": "ELSE IF 1", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "paragraph_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "161", "type": "ELSE", "condition": "and", "conditions": [ ] } ] }, "branch_condition_list": [ { "index": 0, "height": 121.225, "id": "1009" }, { "index": 1, "height": 121.225, "id": "4908" }, { "index": 2, "height": 44, "id": "161" } ] } }, { "id": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "type": "reply-node", "x": 2170, "y": 2480, "properties": { "config": { "fields": [ { "label": "内容", "value": "answer" } ] }, "height": 378, "stepName": "指定回复", "node_data": { "fields": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "directly_return" ], "content": "", "reply_type": "referencing", "is_result": true } } }, { "id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "type": "ai-chat-node", "x": 2160, "y": 3200, "properties": { "config": { "fields": [ { "label": "AI 回答内容", "value": "answer" } ] }, "height": 763, "stepName": "AI 对话", "node_data": { "prompt": "已知信息:\n{{知识库检索.data}}\n问题:\n{{开始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } }, { "id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "type": "ai-chat-node", "x": 2160, "y": 3970, "properties": { "config": { "fields": [ { "label": "AI 回答内容", "value": "answer" } ] }, "height": 763, "stepName": "AI 对话1", "node_data": { "prompt": "{{开始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } } ], "edges": [ { "id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73", "type": "app-edge", "sourceNodeId": "start-node", "targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "startPoint": { "x": 590, "y": 3660 }, "endPoint": { "x": 680, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 590, "y": 3660 }, { "x": 700, "y": 3660 }, { "x": 570, "y": 3210 }, { "x": 680, "y": 3210 } ], "sourceAnchorId": "start-node_right", "targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left" }, { "id": "35cb86dd-f328-429e-a973-12fd7218b696", "type": "app-edge", "sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "startPoint": { "x": 1000, "y": 3210 }, "endPoint": { "x": 1200, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 1000, "y": 3210 }, { "x": 1110, "y": 3210 }, { "x": 1090, "y": 3210 }, { "x": 1200, "y": 3210 } ], "sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right", "targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left" }, { "id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "startPoint": { "x": 1780, "y": 3073.775 }, "endPoint": { "x": 2010, "y": 2480 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3073.775 }, { "x": 1890, "y": 3073.775 }, { "x": 1900, "y": 2480 }, { "x": 2010, "y": 2480 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right", "targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left" }, { "id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "startPoint": { "x": 1780, "y": 3203 }, "endPoint": { "x": 2000, "y": 3200 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3203 }, { "x": 1890, "y": 3203 }, { "x": 1890, "y": 3200 }, { "x": 2000, "y": 3200 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right", "targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left" }, { "id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "startPoint": { "x": 1780, "y": 3293.6124999999997 }, "endPoint": { "x": 2000, "y": 3970 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3293.6124999999997 }, { "x": 1890, "y": 3293.6124999999997 }, { "x": 1890, "y": 3970 }, { "x": 2000, "y": 3970 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right", "targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left" } ] } ================================================ FILE: apps/application/flow/default_workflow_zh_Hant.json ================================================ { "nodes": [ { "id": "base-node", "type": "base-node", "x": 360, "y": 2810, "properties": { "config": { }, "height": 825.6, "stepName": "基本資訊", "node_data": { "desc": "", "name": "maxkbapplication", "prologue": "您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用問題。\n- MaxKB 主要功能有哪些?\n- MaxKB 支援哪些大型語言模型?\n- MaxKB 支援哪些文件類型?" }, "input_field_list": [ ] } }, { "id": "start-node", "type": "start-node", "x": 430, "y": 3660, "properties": { "config": { "fields": [ { "label": "用戶問題", "value": "question" } ], "globalFields": [ { "label": "當前時間", "value": "time" } ] }, "fields": [ { "label": "用戶問題", "value": "question" } ], "height": 276, "stepName": "開始", "globalFields": [ { "label": "當前時間", "value": "time" } ] } }, { "id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "type": "search-dataset-node", "x": 840, "y": 3210, "properties": { "config": { "fields": [ { "label": "檢索結果的分段列表", "value": "paragraph_list" }, { "label": "滿足直接回答的分段列表", "value": "is_hit_handling_method_list" }, { "label": "檢索結果", "value": "data" }, { "label": "滿足直接回答的分段內容", "value": "directly_return" } ] }, "height": 794, "stepName": "知識庫檢索", "node_data": { "dataset_id_list": [ ], "dataset_setting": { "top_n": 3, "similarity": 0.6, "search_mode": "embedding", "max_paragraph_char_number": 5000 }, "question_reference_address": [ "start-node", "question" ], "source_dataset_id_list": [ ] } } }, { "id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "type": "condition-node", "x": 1490, "y": 3210, "properties": { "width": 600, "config": { "fields": [ { "label": "分支名稱", "value": "branch_name" } ] }, "height": 543.675, "stepName": "判斷器", "node_data": { "branch": [ { "id": "1009", "type": "IF", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "is_hit_handling_method_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "4908", "type": "ELSE IF 1", "condition": "and", "conditions": [ { "field": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "paragraph_list" ], "value": "1", "compare": "len_ge" } ] }, { "id": "161", "type": "ELSE", "condition": "and", "conditions": [ ] } ] }, "branch_condition_list": [ { "index": 0, "height": 121.225, "id": "1009" }, { "index": 1, "height": 121.225, "id": "4908" }, { "index": 2, "height": 44, "id": "161" } ] } }, { "id": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "type": "reply-node", "x": 2170, "y": 2480, "properties": { "config": { "fields": [ { "label": "內容", "value": "answer" } ] }, "height": 378, "stepName": "指定回覆", "node_data": { "fields": [ "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "directly_return" ], "content": "", "reply_type": "referencing", "is_result": true } } }, { "id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "type": "ai-chat-node", "x": 2160, "y": 3200, "properties": { "config": { "fields": [ { "label": "AI 回答內容", "value": "answer" } ] }, "height": 763, "stepName": "AI 對話", "node_data": { "prompt": "已知資訊:\n{{知識庫檢索.data}}\n問題:\n{{開始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } }, { "id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "type": "ai-chat-node", "x": 2160, "y": 3970, "properties": { "config": { "fields": [ { "label": "AI 回答內容", "value": "answer" } ] }, "height": 763, "stepName": "AI 對話1", "node_data": { "prompt": "{{開始.question}}", "system": "", "model_id": "", "dialogue_number": 0, "is_result": true } } } ], "edges": [ { "id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73", "type": "app-edge", "sourceNodeId": "start-node", "targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "startPoint": { "x": 590, "y": 3660 }, "endPoint": { "x": 680, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 590, "y": 3660 }, { "x": 700, "y": 3660 }, { "x": 570, "y": 3210 }, { "x": 680, "y": 3210 } ], "sourceAnchorId": "start-node_right", "targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left" }, { "id": "35cb86dd-f328-429e-a973-12fd7218b696", "type": "app-edge", "sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5", "targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "startPoint": { "x": 1000, "y": 3210 }, "endPoint": { "x": 1200, "y": 3210 }, "properties": { }, "pointsList": [ { "x": 1000, "y": 3210 }, { "x": 1110, "y": 3210 }, { "x": 1090, "y": 3210 }, { "x": 1200, "y": 3210 } ], "sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right", "targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left" }, { "id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26", "startPoint": { "x": 1780, "y": 3073.775 }, "endPoint": { "x": 2010, "y": 2480 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3073.775 }, { "x": 1890, "y": 3073.775 }, { "x": 1900, "y": 2480 }, { "x": 2010, "y": 2480 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right", "targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left" }, { "id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb", "startPoint": { "x": 1780, "y": 3203 }, "endPoint": { "x": 2000, "y": 3200 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3203 }, { "x": 1890, "y": 3203 }, { "x": 1890, "y": 3200 }, { "x": 2000, "y": 3200 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right", "targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left" }, { "id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5", "type": "app-edge", "sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b", "targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7", "startPoint": { "x": 1780, "y": 3293.6124999999997 }, "endPoint": { "x": 2000, "y": 3970 }, "properties": { }, "pointsList": [ { "x": 1780, "y": 3293.6124999999997 }, { "x": 1890, "y": 3293.6124999999997 }, { "x": 1890, "y": 3970 }, { "x": 2000, "y": 3970 } ], "sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right", "targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left" } ] } ================================================ FILE: apps/application/flow/i_step_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_step_node.py @date:2024/6/3 14:57 @desc: """ import time import uuid from abc import abstractmethod from hashlib import sha1 from typing import Type, Dict, List from django.core import cache from django.db.models import QuerySet from rest_framework import serializers from rest_framework.exceptions import ValidationError, ErrorDetail from application.flow.common import Answer, NodeChunk from application.models import ApplicationChatUserStats from application.models import ChatRecord, ChatUserType from common.field.common import InstanceField from knowledge.models.knowledge_action import KnowledgeAction, State from tools.models import ToolRecord chat_cache = cache def write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable is not None: for key in step_variable: node.context[key] = step_variable[key] if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'answer' in step_variable: answer = step_variable['answer'] yield answer node.answer_text = answer if global_variable is not None: for key in global_variable: workflow.context[key] = global_variable[key] node.context['run_time'] = time.time() - node.context['start_time'] def is_interrupt(node, step_variable: Dict, global_variable: Dict): return node.type == 'form-node' and not node.context.get('is_submit', False) class WorkFlowPostHandler: def __init__(self, chat_info): self.chat_info = chat_info def handler(self, workflow): workflow_body = workflow.get_body() question = workflow_body.get('question') chat_record_id = workflow_body.get('chat_record_id') chat_id = workflow_body.get('chat_id') details = workflow.get_runtime_details() message_tokens = sum([row.get('message_tokens') for row in details.values() if 'message_tokens' in row and row.get('message_tokens') is not None]) answer_tokens = sum([row.get('answer_tokens') for row in details.values() if 'answer_tokens' in row and row.get('answer_tokens') is not None]) answer_text_list = workflow.get_answer_text_list() answer_text = '\n\n'.join( '\n\n'.join([a.get('content') for a in answer]) for answer in answer_text_list) if workflow.chat_record is not None: chat_record = workflow.chat_record chat_record.problem_text = question chat_record.answer_text = answer_text chat_record.details = details chat_record.message_tokens = message_tokens chat_record.answer_tokens = answer_tokens chat_record.answer_text_list = answer_text_list chat_record.run_time = time.time() - workflow.context['start_time'] else: chat_record = ChatRecord(id=chat_record_id, chat_id=chat_id, problem_text=question, answer_text=answer_text, details=details, message_tokens=message_tokens, answer_tokens=answer_tokens, answer_text_list=answer_text_list, run_time=time.time() - workflow.context.get('start_time') if workflow.context.get( 'start_time') is not None else 0, index=0, ip_address=self.chat_info.ip_address, source=self.chat_info.source) self.chat_info.append_chat_record(chat_record) self.chat_info.set_cache() if not self.chat_info.debug and [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__( workflow_body.get('chat_user_type')): application_public_access_client = (QuerySet(ApplicationChatUserStats) .filter(chat_user_id=workflow_body.get('chat_user_id'), chat_user_type=workflow_body.get('chat_user_type'), application_id=self.chat_info.application_id).first()) if application_public_access_client is not None: application_public_access_client.access_num = application_public_access_client.access_num + 1 application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1 application_public_access_client.save() self.chat_info = None class KnowledgeWorkflowPostHandler(WorkFlowPostHandler): def __init__(self, chat_info, knowledge_action_id): super().__init__(chat_info) self.knowledge_action_id = knowledge_action_id def handler(self, workflow): state = get_workflow_state(workflow) QuerySet(KnowledgeAction).filter(id=self.knowledge_action_id).update( state=state, run_time=time.time() - workflow.context.get('start_time') if workflow.context.get( 'start_time') is not None else 0) def get_tool_workflow_state(workflow): if workflow.is_the_task_interrupted(): return State.REVOKED details = workflow.get_runtime_details() node_list = details.values() all_node = [*node_list, *get_loop_workflow_node(node_list)] err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')]) if err: return State.FAILURE return State.SUCCESS class ToolWorkflowPostHandler(WorkFlowPostHandler): def __init__(self, chat_info, tool_id): super().__init__(chat_info) self.tool_id = tool_id def handler(self, workflow): state = get_tool_workflow_state(workflow) record = ToolRecord(id=self.chat_info.tool_record_id, tool_id=self.tool_id, workspace_id=self.chat_info.workspace_id, source_type=self.chat_info.source_type, source_id=self.chat_info.source_id, state=state, meta={ 'output': workflow.out_context, 'details': workflow.get_runtime_details(), 'answer_text_list': workflow.get_answer_text_list() }) self.chat_info.set_record(record) self.chat_info = None self.tool_id = None def get_loop_workflow_node(node_list): result = [] for item in node_list: if item.get('type') == 'loop-node': for loop_item in item.get('loop_node_data') or []: for inner_item in loop_item.values(): result.append(inner_item) return result def get_workflow_state(workflow): if workflow.is_the_task_interrupted(): return State.REVOKED details = workflow.get_runtime_details() node_list = details.values() all_node = [*node_list, *get_loop_workflow_node(node_list)] err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')]) if err: return State.FAILURE write_is_exist = any([True for value in all_node if value.get('type') == 'knowledge-write-node']) if not write_is_exist: return State.FAILURE return State.SUCCESS class NodeResult: def __init__(self, node_variable: Dict, workflow_variable: Dict, _write_context=write_context, _is_interrupt=is_interrupt): self._write_context = _write_context self.node_variable = node_variable self.workflow_variable = workflow_variable self._is_interrupt = _is_interrupt def write_context(self, node, workflow): return self._write_context(self.node_variable, self.workflow_variable, node, workflow) def is_assertion_result(self): return 'branch_id' in self.node_variable def is_interrupt_exec(self, current_node): """ 是否中断执行 @param current_node: @return: """ return self._is_interrupt(current_node, self.node_variable, self.workflow_variable) class ReferenceAddressSerializer(serializers.Serializer): node_id = serializers.CharField(required=True, label="节点id") fields = serializers.ListField( child=serializers.CharField(required=True, label="节点字段"), required=True, label="节点字段数组") class FlowParamsSerializer(serializers.Serializer): # 历史对答 history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), label="历史对答") question = serializers.CharField(required=True, label="用户问题") chat_id = serializers.CharField(required=True, label="对话id") chat_record_id = serializers.CharField(required=True, label="对话记录id") stream = serializers.BooleanField(required=True, label="流式输出") chat_user_id = serializers.CharField(required=False, label="对话用户id") chat_user_type = serializers.CharField(required=False, label="对话用户类型") workspace_id = serializers.CharField(required=True, label="工作空间id") application_id = serializers.CharField(required=True, label="应用id") re_chat = serializers.BooleanField(required=True, label="换个答案") debug = serializers.BooleanField(required=True, label="是否debug") class KnowledgeFlowParamsSerializer(serializers.Serializer): knowledge_id = serializers.UUIDField(required=True, label="知识库id") workspace_id = serializers.CharField(required=True, label="工作空间id") knowledge_action_id = serializers.UUIDField(required=True, label="知识库任务执行器id") data_source = serializers.DictField(required=True, label="数据源") knowledge_base = serializers.DictField(required=False, label="知识库设置") class ToolFlowParamsSerializer(serializers.Serializer): tool_id = serializers.UUIDField(required=True, label="工具id") workspace_id = serializers.CharField(required=True, label="工作空间id") class INode: view_type = 'many_view' @abstractmethod def save_context(self, details, workflow_manage): pass def get_answer_list(self) -> List[Answer] | None: if self.answer_text is None: return None reasoning_content_enable = self.context.get('model_setting', {}).get('reasoning_content_enable', False) return [ Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params.get('chat_record_id'), {}, self.runtime_node_id, self.context.get('reasoning_content', '') if reasoning_content_enable else '')] def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None, get_node_params=lambda node: node.properties.get('node_data'), salt=None): # 当前步骤上下文,用于存储当前步骤信息 self.status = 200 self.err_message = '' self.node = node self.node_params = get_node_params(node) self.workflow_params = workflow_params self.workflow_manage = workflow_manage self.node_params_serializer = None self.flow_params_serializer = None self.context = {} self.answer_text = None self.id = node.id if up_node_id_list is None: up_node_id_list = [] self.up_node_id_list = up_node_id_list self.node_chunk = NodeChunk() self.runtime_node_id = sha1(uuid.NAMESPACE_DNS.bytes + bytes(str(uuid.uuid5(uuid.NAMESPACE_DNS, "".join([*sorted(up_node_id_list), node.id]))), "utf-8")).hexdigest() + ( "__" + str(salt) if salt is not None else '') def valid_args(self, node_params, flow_params): flow_params_serializer_class = self.get_flow_params_serializer_class() node_params_serializer_class = self.get_node_params_serializer_class() if flow_params_serializer_class is not None and flow_params is not None: self.flow_params_serializer = flow_params_serializer_class(data=flow_params) self.flow_params_serializer.is_valid(raise_exception=True) if node_params_serializer_class is not None: self.node_params_serializer = node_params_serializer_class(data=node_params) self.node_params_serializer.is_valid(raise_exception=True) if self.node.properties.get('status', 200) != 200: raise ValidationError(ErrorDetail(f'节点{self.node.properties.get("stepName")} 不可用')) def get_reference_field(self, fields: List[str]): return self.get_field(self.context, fields) @staticmethod def get_field(obj, fields: List[str]): for field in fields: value = obj.get(field) if value is None: return None else: obj = value return obj @abstractmethod def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: pass def get_flow_params_serializer_class(self) -> Type[serializers.Serializer]: return self.workflow_manage.get_params_serializer_class() def get_write_error_context(self, e): self.status = 500 self.answer_text = str(e) self.err_message = str(e) current_time = time.time() self.context['run_time'] = current_time - (self.context.get('start_time') or current_time) def write_error_context(answer, status=200): pass return write_error_context def run(self) -> NodeResult: """ :return: 执行结果 """ start_time = time.time() self.context['start_time'] = start_time result = self._run() self.context['run_time'] = time.time() - start_time return result def _run(self): result = self.execute() return result def execute(self, **kwargs) -> NodeResult: pass def get_details(self, index: int, **kwargs): """ 运行详情 :return: 步骤详情 """ return {} ================================================ FILE: apps/application/flow/knowledge_loop_workflow_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: workflow_manage.py @date:2024/1/9 17:40 @desc: """ from application.flow.i_step_node import KnowledgeFlowParamsSerializer from application.flow.loop_workflow_manage import LoopWorkflowManage class KnowledgeLoopWorkflowManage(LoopWorkflowManage): def get_params_serializer_class(self): return KnowledgeFlowParamsSerializer def get_source_type(self): return "KNOWLEDGE" def get_source_id(self): return self.params.get('knowledge_id') ================================================ FILE: apps/application/flow/knowledge_workflow_manage.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: Knowledge_workflow_manage.py @date:2025/11/13 19:02 @desc: """ import time import traceback from concurrent.futures import ThreadPoolExecutor from django.db.models import QuerySet from django.utils.translation import get_language from application.flow.common import Workflow from application.flow.i_step_node import WorkFlowPostHandler, KnowledgeFlowParamsSerializer, NodeResult from application.flow.workflow_manage import WorkflowManage from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse from knowledge.models.knowledge_action import KnowledgeAction, State executor = ThreadPoolExecutor(max_workers=200) class KnowledgeWorkflowManage(WorkflowManage): def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler, base_to_response: BaseToResponse = SystemToResponse(), start_node_id=None, start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False): super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None, None, None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted) def get_params_serializer_class(self): return KnowledgeFlowParamsSerializer def get_start_node(self): start_node_list = [node for node in self.flow.nodes if self.params.get('data_source', {}).get('node_id') == node.id] return start_node_list[0] def run(self): self.context['start_time'] = time.time() executor.submit(self._run) def _run(self): QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update( state=State.STARTED) language = get_language() self.run_chain_async(self.start_node, None, language) while self.is_run(): pass self.work_flow_post_handler.handler(self) @staticmethod def get_node_details(current_node, node, index): if current_node == node: return { 'name': node.node.properties.get('stepName'), "index": index, 'run_time': 0, 'type': node.type, 'status': 202, 'err_message': "" } return node.get_details(index) def run_chain(self, current_node, node_result_future=None): QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update( details=self.get_runtime_details(lambda node, index: self.get_node_details(current_node, node, index))) if node_result_future is None: node_result_future = self.run_node_future(current_node) try: result = self.hand_node_result(current_node, node_result_future) return result except Exception as e: traceback.print_exc() return None def hand_node_result(self, current_node, node_result_future): try: current_result = node_result_future.result() result = current_result.write_context(current_node, self) if result is not None: # 阻塞获取结果 list(result) if current_node.status == 500: enableException = current_node.node.properties.get('enableException') if not enableException: return None current_node.context['exception_message'] = current_node.err_message current_node.context['branch_id'] = 'exception' r = NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {}, _is_interrupt=lambda node, step_variable, global_variable: False) r.write_context(current_node, self) return r if self.is_the_task_interrupted(): current_node.status = 201 return None return current_result except Exception as e: traceback.print_exc() self.status = 500 current_node.get_write_error_context(e) self.answer += str(e) if self.is_the_task_interrupted(): current_node.status = 201 return None enableException = current_node.node.properties.get('enableException') if enableException: current_node.context['exception_message'] = current_node.err_message current_node.context['branch_id'] = 'exception' return NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {}, _is_interrupt=lambda node, step_variable, global_variable: False) QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update(state=State.FAILURE) finally: current_node.node_chunk.end() QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update( details=self.get_runtime_details()) def get_source_type(self): return "KNOWLEDGE" def get_source_id(self): return self.params.get('knowledge_id') ================================================ FILE: apps/application/flow/loop_workflow_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: workflow_manage.py @date:2024/1/9 17:40 @desc: """ from concurrent.futures import ThreadPoolExecutor from typing import List from django.db import close_old_connections from django.utils.translation import get_language from langchain_core.prompts import PromptTemplate from application.flow.common import Workflow from application.flow.i_step_node import WorkFlowPostHandler, INode from application.flow.step_node import get_node from application.flow.workflow_manage import WorkflowManage from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse executor = ThreadPoolExecutor(max_workers=200) class NodeResultFuture: def __init__(self, r, e, status=200): self.r = r self.e = e self.status = status def result(self): if self.status == 200: return self.r else: raise self.e def await_result(result, timeout=1): try: result.result(timeout) return False except Exception as e: return True class NodeChunkManage: def __init__(self, work_flow): self.node_chunk_list = [] self.current_node_chunk = None self.work_flow = work_flow def add_node_chunk(self, node_chunk): self.node_chunk_list.append(node_chunk) def contains(self, node_chunk): return self.node_chunk_list.__contains__(node_chunk) def pop(self): if self.current_node_chunk is None: try: current_node_chunk = self.node_chunk_list.pop(0) self.current_node_chunk = current_node_chunk except IndexError as e: pass if self.current_node_chunk is not None: try: chunk = self.current_node_chunk.chunk_list.pop(0) return chunk except IndexError as e: if self.current_node_chunk.is_end(): self.current_node_chunk = None if self.work_flow.answer_is_not_empty(): chunk = self.work_flow.base_to_response.to_stream_chunk_response( self.work_flow.params['chat_id'], self.work_flow.params['chat_record_id'], '\n\n', False, 0, 0) self.work_flow.append_answer('\n\n') return chunk return self.pop() return None class LoopWorkflowManage(WorkflowManage): def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler, parentWorkflowManage, loop_params, get_loop_context, base_to_response: BaseToResponse = SystemToResponse(), start_node_id=None, start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False): self.parentWorkflowManage = parentWorkflowManage self.loop_params = loop_params self.get_loop_context = get_loop_context self.loop_field_list = [] super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None, None, None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted) def get_node_cls_by_id(self, node_id, up_node_id_list=None, get_node_params=lambda node: node.properties.get('node_data')): for node in self.flow.nodes: if node.id == node_id: node_instance = get_node(node.type, self.flow.workflow_mode)(node, self.params, self, up_node_id_list, get_node_params, salt=self.get_index()) return node_instance return None def stream(self): close_old_connections() language = get_language() self.run_chain_async(self.start_node, None, language) return self.await_result(is_cleanup=False) def get_index(self): return self.loop_params.get('index') def get_start_node(self): start_node_list = [node for node in self.flow.nodes if ['loop-start-node'].__contains__(node.type)] return start_node_list[0] def get_reference_field(self, node_id: str, fields: List[str]): """ @param node_id: 节点id @param fields: 字段 @return: """ if node_id == 'global': return self.parentWorkflowManage.get_reference_field(node_id, fields) elif node_id == 'chat': return self.parentWorkflowManage.get_reference_field(node_id, fields) elif node_id == 'loop': loop_context = self.get_loop_context() return INode.get_field(loop_context, fields) else: node = self.get_node_by_id(node_id) if node: return node.get_reference_field(fields) return self.parentWorkflowManage.get_reference_field(node_id, fields) def get_workflow_content(self): context = { 'global': self.context, 'chat': self.chat_context, 'loop': self.get_loop_context(), } for node in self.node_context: context[node.id] = node.context return context def init_fields(self): super().init_fields() loop_field_list = [] loop_start_node = self.flow.get_node('loop-start-node') loop_input_field_list = loop_start_node.properties.get('loop_input_field_list') node_name = loop_start_node.properties.get('stepName') node_id = loop_start_node.id if loop_input_field_list is not None: for f in loop_input_field_list: loop_field_list.append( {'label': f.get('label'), 'value': f.get('field'), 'node_id': node_id, 'node_name': node_name}) self.loop_field_list = loop_field_list def reset_prompt(self, prompt: str): prompt = super().reset_prompt(prompt) for field in self.loop_field_list: chatLabel = f"loop.{field.get('value')}" chatValue = f"context.get('loop').get('{field.get('value', '')}','')" prompt = prompt.replace(chatLabel, chatValue) prompt = self.parentWorkflowManage.reset_prompt(prompt) return prompt def generate_prompt(self, prompt: str): """ 格式化生成提示词 @param prompt: 提示词信息 @return: 格式化后的提示词 """ context = {**self.get_workflow_content(), **self.parentWorkflowManage.get_workflow_content()} prompt = self.reset_prompt(prompt) prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2') value = prompt_template.format(context=context) return value def get_source_type(self): return "APPLICATION" def get_source_id(self): return self.params.get('application_id') ================================================ FILE: apps/application/flow/step_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/6/7 14:43 @desc: """ from .ai_chat_step_node import * from .application_node import BaseApplicationNode from .condition_node import * from .data_source_local_node.impl.base_data_source_local_node import BaseDataSourceLocalNode from .data_source_web_node.impl.base_data_source_web_node import BaseDataSourceWebNode from .direct_reply_node import * from .document_extract_node import * from .form_node import * from .image_generate_step_node import * from .image_to_video_step_node import BaseImageToVideoNode from .image_understand_step_node import * from .intent_node import * from .knowledge_write_node.impl.base_knowledge_write_node import BaseKnowledgeWriteNode from .loop_break_node import BaseLoopBreakNode from .loop_continue_node import BaseLoopContinueNode from .loop_node import * from .loop_start_node import * from .mcp_node import BaseMcpNode from .parameter_extraction_node import BaseParameterExtractionNode from .question_node import * from .reranker_node import * from .search_document_node import BaseSearchDocumentNode from .search_knowledge_node import * 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 .text_to_video_step_node.impl.base_text_to_video_node import BaseTextToVideoNode from .tool_lib_node import * from .tool_node import * from .tool_workflow_lib_node import BaseToolWorkflowLibNodeNode from .variable_aggregation_node.impl.base_variable_aggregation_node import BaseVariableAggregationNode from .variable_assign_node import BaseVariableAssignNode from .variable_splitting_node import BaseVariableSplittingNode from .video_understand_step_node import BaseVideoUnderstandNode from .document_split_node import BaseDocumentSplitNode from .tool_start_node import BaseToolStartStepNode node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseSearchDocumentNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode, BaseToolNodeNode, BaseToolLibNodeNode, BaseRerankerNode, BaseApplicationNode, BaseDocumentExtractNode, BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode, BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode, BaseTextToVideoNode, BaseImageToVideoNode, BaseVideoUnderstandNode, BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode, BaseLoopContinueNode, BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode, BaseVariableAggregationNode, BaseDataSourceLocalNode, BaseDataSourceWebNode, BaseKnowledgeWriteNode, BaseDocumentSplitNode, BaseToolStartStepNode, BaseToolWorkflowLibNodeNode] node_map = {n.type: {w: n for w in n.support} for n in node_list} def get_node(node_type, workflow_model): return node_map.get(node_type).get(workflow_model) ================================================ FILE: apps/application/flow/step_node/ai_chat_step_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:29 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_chat_node.py @date:2024/6/4 13:58 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class ChatNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model id")) model_id_type = serializers.CharField(required=False, default='custom', label=_("Model id type")) model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True, label=_("Reference Field")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Role Setting")) prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) model_setting = serializers.DictField(required=False, label='Model settings') dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Context Type")) mcp_servers = serializers.JSONField(required=False, label=_("MCP Server")) mcp_tool_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("MCP Tool ID")) mcp_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("MCP Tool IDs"), ) mcp_source = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("MCP Source")) tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("Tool IDs"), ) application_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("App IDs"), ) skill_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("Skill IDs"), ) mcp_output_enable = serializers.BooleanField(required=False, default=True, label=_("Whether to enable MCP output")) class IChatNode(INode): type = 'ai-chat-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ChatNodeSerializer def _run(self): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, model_id_type=None, model_id_reference=None, dialogue_type=None, model_setting=None, mcp_servers=None, mcp_tool_id=None, mcp_tool_ids=None, mcp_source=None, tool_ids=None, application_ids=None, skill_tool_ids=None, mcp_output_enable=True, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/ai_chat_step_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:34 @desc: """ from .base_chat_node import BaseChatNode ================================================ FILE: apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_question_node.py @date:2024/6/4 14:30 @desc: """ import json import re import time from functools import reduce from typing import List, Dict from application.flow.i_step_node import NodeResult, INode from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode from application.flow.tools import Reasoning, mcp_response_generator from application.models import Application, ApplicationApiKey, ApplicationAccessToken from common.exception.app_exception import AppApiException from common.utils.rsa_util import rsa_long_decrypt from common.utils.shared_resource_auth import filter_authorized_ids from common.utils.tool_code import ToolExecutor from django.db.models import QuerySet from django.utils.translation import gettext as _ from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage from models_provider.models import Model from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id from tools.models import Tool def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, reasoning_content: str): chat_model = node_variable.get('chat_model') message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list')) answer_tokens = chat_model.get_num_tokens(answer) node.context['message_tokens'] = message_tokens node.context['answer_tokens'] = answer_tokens node.context['answer'] = answer node.context['question'] = node_variable['question'] node.context['run_time'] = time.time() - node.context['start_time'] node.context['reasoning_content'] = reasoning_content if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = '' reasoning_content = '' model_setting = node.context.get('model_setting', {'reasoning_content_enable': False, 'reasoning_content_end': '', 'reasoning_content_start': ''}) reasoning = Reasoning(model_setting.get('reasoning_content_start', ''), model_setting.get('reasoning_content_end', '')) response_reasoning_content = False for chunk in response: if workflow.is_the_task_interrupted(): break reasoning_chunk = reasoning.get_reasoning_content(chunk) content_chunk = reasoning_chunk.get('content') if 'reasoning_content' in chunk.additional_kwargs: response_reasoning_content = True reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') else: reasoning_content_chunk = reasoning_chunk.get('reasoning_content') answer += content_chunk if reasoning_content_chunk is None: reasoning_content_chunk = '' reasoning_content += reasoning_content_chunk yield {'content': content_chunk, 'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable', False) else ''} reasoning_chunk = reasoning.get_end_reasoning_content() answer += reasoning_chunk.get('content') reasoning_content_chunk = "" if not response_reasoning_content: reasoning_content_chunk = reasoning_chunk.get( 'reasoning_content') yield {'content': reasoning_chunk.get('content'), 'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable', False) else ''} _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result') model_setting = node.context.get('model_setting', {'reasoning_content_enable': False, 'reasoning_content_end': '', 'reasoning_content_start': ''}) reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end')) reasoning_result = reasoning.get_reasoning_content(response) reasoning_result_end = reasoning.get_end_reasoning_content() content = reasoning_result.get('content') + reasoning_result_end.get('content') meta = {**response.response_metadata, **response.additional_kwargs} if 'reasoning_content' in meta: reasoning_content = (meta.get('reasoning_content', '') or '') else: reasoning_content = (reasoning_result.get('reasoning_content') or '') + ( reasoning_result_end.get('reasoning_content') or '') _write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content) def get_default_model_params_setting(model_id): model = QuerySet(Model).filter(id=model_id).first() credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = credential.get_model_params_setting_form( model.model_name).get_default_form_data() return model_params_setting def get_node_message(chat_record, runtime_node_id): node_details = chat_record.get_node_details_runtime_node_id(runtime_node_id) if node_details is None: return [] return [HumanMessage(node_details.get('question')), AIMessage(node_details.get('answer'))] def get_workflow_message(chat_record): return [chat_record.get_human_message(), chat_record.get_ai_message()] def get_message(chat_record, dialogue_type, runtime_node_id): return get_node_message(chat_record, runtime_node_id) if dialogue_type == 'NODE' else get_workflow_message( chat_record) class BaseChatNode(IChatNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['question'] = details.get('question') self.context['reasoning_content'] = details.get('reasoning_content') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, model_id_type=None, model_id_reference=None, dialogue_type=None, model_setting=None, mcp_servers=None, mcp_tool_id=None, mcp_tool_ids=None, mcp_source=None, tool_ids=None, application_ids=None, skill_tool_ids=None, mcp_output_enable=True, **kwargs) -> NodeResult: if dialogue_type is None: dialogue_type = 'WORKFLOW' if model_id_type == 'reference' and model_id_reference: reference_data = self.workflow_manage.get_reference_field( model_id_reference[0], model_id_reference[1:], ) if reference_data and isinstance(reference_data, dict): model_id = reference_data.get('model_id', model_id) model_params_setting = reference_data.get('model_params_setting') if model_params_setting is None and model_id: model_params_setting = get_default_model_params_setting(model_id) if model_setting is None: model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '', 'reasoning_content_start': ''} self.context['model_setting'] = model_setting workspace_id = self.workflow_manage.get_body().get('workspace_id') chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type, self.runtime_node_id) self.context['history_message'] = [{'content': message.content, 'role': message.type} for message in (history_message if history_message is not None else [])] question = self.generate_prompt_question(prompt) self.context['question'] = question.content system = self.workflow_manage.generate_prompt(system) self.context['system'] = system message_list = self.generate_message_list(system, prompt, history_message) self.context['message_list'] = message_list # 过滤tool_id all_tool_ids = list(set( (mcp_tool_ids or []) + (tool_ids or []) + (skill_tool_ids or []) + ([mcp_tool_id] if mcp_tool_id else []) )) authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id)) mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set] tool_ids = [i for i in (tool_ids or []) if i in authorized_set] skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set] mcp_tool_id = mcp_tool_id if (mcp_tool_id and mcp_tool_id in authorized_set) else None # 处理 MCP 请求 mcp_result = self._handle_mcp_request( mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids, application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, history_message, question, chat_id ) if mcp_result: return mcp_result if stream: r = chat_model.stream(message_list) return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list, 'question': question.content}, {}, _write_context=write_context_stream) else: r = chat_model.invoke(message_list) return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list, 'history_message': [{'content': message.content, 'role': message.type} for message in (history_message if history_message is not None else [])], 'question': question.content}, {}, _write_context=write_context) def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids, application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, history_message, question, chat_id): mcp_servers_config = {} # 迁移过来mcp_source是None if mcp_source is None: mcp_source = 'custom' # 兼容老数据 if not mcp_tool_ids: mcp_tool_ids = [] if mcp_tool_id: mcp_tool_ids = list(set(mcp_tool_ids + [mcp_tool_id])) if mcp_source == 'custom' and mcp_servers and '"stdio"' not in mcp_servers: mcp_servers_config = json.loads(mcp_servers) mcp_servers_config = self.handle_variables(mcp_servers_config) elif mcp_tool_ids: mcp_tools = QuerySet(Tool).filter(id__in=mcp_tool_ids).values() for mcp_tool in mcp_tools: if mcp_tool and mcp_tool['is_active']: mcp_servers_config = {**mcp_servers_config, **json.loads(mcp_tool['code'])} mcp_servers_config = self.handle_variables(mcp_servers_config) tool_init_params = {} if tool_ids and len(tool_ids) > 0: # 如果有工具ID,则将其转换为MCP self.context['tool_ids'] = tool_ids for tool_id in tool_ids: tool = QuerySet(Tool).filter(id=tool_id).first() if not tool.is_active: continue executor = ToolExecutor() if tool.init_params is not None: params = json.loads(rsa_long_decrypt(tool.init_params)) tool_init_params = json.loads(rsa_long_decrypt(tool.init_params)) else: params = {} tool_config = executor.get_tool_mcp_config(tool, params) mcp_servers_config[str(tool.id)] = tool_config if application_ids and len(application_ids) > 0: self.context['application_ids'] = application_ids for application_id in application_ids: app = QuerySet(Application).filter(id=application_id, is_publish=True).first() if app is None: continue app_key = QuerySet(ApplicationApiKey).filter(application_id=application_id, is_active=True).first() if app_key is not None: api_key = app_key.secret_key application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=app_key.application_id ).first() if application_access_token is not None and application_access_token.authentication: raise AppApiException( 500, _('Agent 【{name}】 access token authentication is not supported for agent tool').format( name=app.name) ) else: raise AppApiException( 500, _('Agent Key is required for agent tool 【{name}】').format(name=app.name) ) executor = ToolExecutor() app_config = executor.get_app_mcp_config(api_key) mcp_servers_config[app.name] = app_config if skill_tool_ids and len(skill_tool_ids) > 0: self.context['skill_tool_ids'] = skill_tool_ids skill_file_items = [] for tool_id in skill_tool_ids: tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first() if tool is None or tool.is_active is False: continue init_params_default_value = {i["field"]: i.get('default_value') for i in tool.init_field_list} if tool.init_params is not None: params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params)) else: params = init_params_default_value skill_file_items.append({ 'tool_id': str(tool.id), 'file_id': tool.code, 'params': params }) mcp_servers_config['skills'] = skill_file_items if len(mcp_servers_config) > 0: # 安全获取 application application_id = None if (self.workflow_manage and self.workflow_manage.work_flow_post_handler and self.workflow_manage.work_flow_post_handler.chat_info): application_id = self.workflow_manage.work_flow_post_handler.chat_info.application.id knowledge_id = self.workflow_params.get('knowledge_id') source_id = application_id or knowledge_id source_type = 'APPLICATION' if application_id else 'KNOWLEDGE' r = mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable, tool_init_params, source_id, source_type, chat_id) return NodeResult( {'result': r, 'chat_model': chat_model, 'message_list': message_list, 'history_message': [{'content': message.content, 'role': message.type} for message in (history_message if history_message is not None else [])], 'question': question.content}, {}, _write_context=write_context_stream) return None def handle_variables(self, tool_params): # 处理参数中的变量 for k, v in tool_params.items(): if type(v) == str: tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k]) if type(v) == dict: self.handle_variables(v) if (type(v) == list) and (type(v[0]) == str): tool_params[k] = self.get_reference_content(v) return tool_params def get_reference_content(self, fields: List[str]): return str(self.workflow_manage.get_reference_field( fields[0], fields[1:])) @staticmethod def get_history_message(history_chat_record, dialogue_number, dialogue_type, runtime_node_id): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ get_message(history_chat_record[index], dialogue_type, runtime_node_id) for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) for message in history_message: if isinstance(message.content, str): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message def generate_prompt_question(self, prompt): return HumanMessage(self.workflow_manage.generate_prompt(prompt)) def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'system': self.context.get('system'), 'history_message': self.context.get('history_message'), 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'reasoning_content': self.context.get('reasoning_content'), 'enableException': self.node.properties.get('enableException'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message } ================================================ FILE: apps/application/flow/step_node/application_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/application_node/i_application_node.py ================================================ # coding=utf-8 from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ from application.models import ChatSourceChoices class ApplicationNodeSerializer(serializers.Serializer): application_id = serializers.CharField(required=True, label=_("Application ID")) question_reference_address = serializers.ListField(required=True, label=_("User Questions")) api_input_field_list = serializers.ListField(required=False, label=_("API Input Fields")) user_input_field_list = serializers.ListField(required=False, label=_("User Input Fields")) image_list = serializers.ListField(required=False, label=_("picture")) document_list = serializers.ListField(required=False, label=_("document")) audio_list = serializers.ListField(required=False, label=_("Audio")) video_list = serializers.ListField(required=False, label=_("Video")) child_node = serializers.DictField(required=False, allow_null=True, label=_("Child Nodes")) node_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data")) class IApplicationNode(INode): type = 'application-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ApplicationNodeSerializer def _run(self): question = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('question_reference_address')[0], self.node_params_serializer.data.get('question_reference_address')[1:]) kwargs = {} for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []): value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else '' kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(value, api_input_field['value'][ 1:]) if value != '' else '' for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []): value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else '' kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(value, user_input_field['value'][ 1:]) if value != '' else '' # 判断是否包含这个属性 app_document_list = self.node_params_serializer.data.get('document_list', []) if app_document_list and len(app_document_list) > 0: app_document_list = self.workflow_manage.get_reference_field( app_document_list[0], app_document_list[1:]) for document in app_document_list: if 'file_id' not in document: raise ValueError( _("Parameter value error: The uploaded document lacks file_id, and the document upload fails")) app_image_list = self.node_params_serializer.data.get('image_list', []) if app_image_list and len(app_image_list) > 0: app_image_list = self.workflow_manage.get_reference_field( app_image_list[0], app_image_list[1:]) for image in app_image_list: if 'file_id' not in image: raise ValueError( _("Parameter value error: The uploaded image lacks file_id, and the image upload fails")) app_audio_list = self.node_params_serializer.data.get('audio_list', []) if app_audio_list and len(app_audio_list) > 0: app_audio_list = self.workflow_manage.get_reference_field( app_audio_list[0], app_audio_list[1:]) for audio in app_audio_list: if 'file_id' not in audio: raise ValueError( _("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails.")) app_video_list = self.node_params_serializer.data.get('video_list', []) if app_video_list and len(app_video_list) > 0: app_video_list = self.workflow_manage.get_reference_field( app_video_list[0], app_video_list[1:] ) for video in app_video_list: if 'file_id' not in video: raise ValueError( _("Parameter value error: The uploaded video lacks file_id, and the video upload fails.")) return self.execute(**{**self.flow_params_serializer.data, **self.node_params_serializer.data}, app_document_list=app_document_list, app_image_list=app_image_list, app_audio_list=app_audio_list, app_video_list=app_video_list, ip_address=self.workflow_params.get('ip_address') or '-', source=self.workflow_params.get('source') or {"type": ChatSourceChoices.ONLINE.value}, message=str(question), **kwargs) def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, app_document_list=None, app_image_list=None, app_audio_list=None, app_video_list=None, child_node=None, node_data=None, ip_address=None, source=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/application_node/impl/__init__.py ================================================ # coding=utf-8 from .base_application_node import BaseApplicationNode ================================================ FILE: apps/application/flow/step_node/application_node/impl/base_application_node.py ================================================ # coding=utf-8 import json import re import time import uuid from typing import Dict, List from django.utils.translation import gettext as _ from application.flow.common import Answer from application.flow.i_step_node import NodeResult, INode from application.flow.step_node.application_node.i_application_node import IApplicationNode from application.models import Chat, ChatSourceChoices def string_to_uuid(input_str): return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str)) def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict): return node_variable.get('is_interrupt_exec', False) def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, reasoning_content: str): result = node_variable.get('result') node.context['application_node_dict'] = node_variable.get('application_node_dict') node.context['node_dict'] = node_variable.get('node_dict', {}) node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec') node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0) node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0) node.context['answer'] = answer node.context['result'] = answer node.context['reasoning_content'] = reasoning_content node.context['question'] = node_variable['question'] node.context['run_time'] = time.time() - node.context['start_time'] if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = '' reasoning_content = '' usage = {} node_child_node = {} application_node_dict = node.context.get('application_node_dict', {}) is_interrupt_exec = False for chunk in response: # 先把流转成字符串 response_content = chunk.decode('utf-8')[6:] response_content = json.loads(response_content) content = (response_content.get('content', '') or '') runtime_node_id = response_content.get('runtime_node_id', '') chat_record_id = response_content.get('chat_record_id', '') child_node = response_content.get('child_node') view_type = response_content.get('view_type') node_type = response_content.get('node_type') real_node_id = response_content.get('real_node_id') node_is_end = response_content.get('node_is_end', False) _reasoning_content = (response_content.get('reasoning_content', '') or '') if node_type == 'form-node': is_interrupt_exec = True answer += content reasoning_content += _reasoning_content node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'child_node': child_node} if real_node_id is not None: application_node = application_node_dict.get(real_node_id, None) if application_node is None: application_node_dict[real_node_id] = {'content': content, 'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'child_node': child_node, 'index': len(application_node_dict), 'view_type': view_type, 'reasoning_content': _reasoning_content} else: application_node['content'] += content application_node['reasoning_content'] += _reasoning_content yield {'content': content, 'node_type': node_type, 'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'reasoning_content': _reasoning_content, 'child_node': child_node, 'real_node_id': real_node_id, 'node_is_end': node_is_end, 'view_type': view_type} usage = response_content.get('usage', {}) node_variable['result'] = {'usage': usage} node_variable['is_interrupt_exec'] = is_interrupt_exec node_variable['child_node'] = node_child_node node_variable['application_node_dict'] = application_node_dict _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result', {}).get('data', {}) node_variable['result'] = {'usage': {'completion_tokens': response.get('completion_tokens'), 'prompt_tokens': response.get('prompt_tokens')}} answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。" reasoning_content = response.get('reasoning_content', '') answer_list = response.get('answer_list', []) node_variable['application_node_dict'] = {answer.get('real_node_id'): {**answer, 'index': index} for answer, index in zip(answer_list, range(len(answer_list)))} _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content) def reset_application_node_dict(application_node_dict, runtime_node_id, node_data): try: if application_node_dict is None: return for key in application_node_dict: application_node = application_node_dict[key] if application_node.get('runtime_node_id') == runtime_node_id: content: str = application_node.get('content') match = re.search('.*?', content) if match: form_setting_str = match.group().replace('', '').replace('', '') form_setting = json.loads(form_setting_str) form_setting['is_submit'] = True form_setting['form_data'] = node_data value = f'{json.dumps(form_setting)}' res = re.sub('.*?', '${value}', content) application_node['content'] = res.replace('${value}', value) except Exception as e: pass class BaseApplicationNode(IApplicationNode): def get_answer_list(self) -> List[Answer] | None: if self.answer_text is None: return None application_node_dict = self.context.get('application_node_dict') if application_node_dict is None or len(application_node_dict) == 0: return [ Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], self.context.get('child_node'), self.runtime_node_id, '')] else: return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id, self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'), 'chat_record_id': n.get('chat_record_id') , 'child_node': n.get('child_node')}, n.get('real_node_id'), n.get('reasoning_content', '')) for n in sorted(application_node_dict.values(), key=lambda item: item.get('index'))] def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['result'] = details.get('answer') self.context['question'] = details.get('question') self.context['type'] = details.get('type') self.context['reasoning_content'] = details.get('reasoning_content') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def get_chat_asker(self, kwargs): asker = kwargs.get('asker') if asker: if isinstance(asker, dict): return asker return {'username': asker} return self.workflow_manage.work_flow_post_handler.chat_info.get_chat_user() def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, chat_user_id, chat_user_type, app_document_list=None, app_image_list=None, app_audio_list=None, app_video_list=None, child_node=None, node_data=None, ip_address=None, source=None, **kwargs) -> NodeResult: from chat.serializers.chat import ChatSerializers if application_id == self.workflow_manage.get_body().get('application_id'): raise Exception(_("The sub application cannot use the current node")) # 生成嵌入应用的chat_id current_chat_id = string_to_uuid(chat_id + application_id) Chat.objects.get_or_create(id=current_chat_id, defaults={ 'application_id': application_id, 'abstract': message[0:1024], 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'ip_address': ip_address, 'source': source, 'asker': self.get_chat_asker(kwargs) }) if app_document_list is None: app_document_list = [] if app_image_list is None: app_image_list = [] if app_audio_list is None: app_audio_list = [] if app_video_list is None: app_video_list = [] runtime_node_id = None record_id = None child_node_value = None if child_node is not None: runtime_node_id = child_node.get('runtime_node_id') record_id = child_node.get('chat_record_id') child_node_value = child_node.get('child_node') application_node_dict = self.context.get('application_node_dict') reset_application_node_dict(application_node_dict, runtime_node_id, node_data) response = ChatSerializers(data={ "chat_id": current_chat_id, "chat_user_id": chat_user_id, 'chat_user_type': chat_user_type, 'application_id': application_id, 'ip_address': ip_address, 'source': source, 'debug': False }).chat(instance= {'message': message, 're_chat': re_chat, 'stream': stream, 'document_list': [*app_document_list], 'image_list': [*app_image_list], 'audio_list': [*app_audio_list], 'video_list': [*app_video_list], 'runtime_node_id': runtime_node_id, 'chat_record_id': record_id, 'child_node': child_node_value, 'node_data': node_data, 'form_data': kwargs} ) if response.status_code == 200: if stream: content_generator = response.streaming_content return NodeResult({'result': content_generator, 'question': message}, {}, _write_context=write_context_stream, _is_interrupt=_is_interrupt_exec) else: data = json.loads(response.content) return NodeResult({'result': data, 'question': message}, {}, _write_context=write_context, _is_interrupt=_is_interrupt_exec) def get_details(self, index: int, **kwargs): global_fields = [] for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []): value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else '' global_fields.append({ 'label': api_input_field['variable'], 'key': api_input_field['variable'], 'value': self.workflow_manage.get_reference_field( value, api_input_field['value'][1:] ) if value != '' else '' }) for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []): value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else '' global_fields.append({ 'label': user_input_field['label'], 'key': user_input_field['field'], 'value': self.workflow_manage.get_reference_field( value, user_input_field['value'][1:] ) if value != '' else '' }) return { 'name': self.node.properties.get('stepName'), "index": index, "info": self.node.properties.get('node_data'), 'run_time': self.context.get('run_time'), 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'reasoning_content': self.context.get('reasoning_content'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'global_fields': global_fields, 'document_list': self.workflow_manage.document_list, 'image_list': self.workflow_manage.image_list, 'audio_list': self.workflow_manage.audio_list, 'video_list': self.workflow_manage.video_list, 'application_node_dict': self.context.get('application_node_dict'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/condition_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/6/7 14:43 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/condition_node/i_condition_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_condition_node.py @date:2024/6/7 9:54 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode class ConditionSerializer(serializers.Serializer): compare = serializers.CharField(required=True, label=_("Comparator")) value = serializers.CharField(required=True, label=_("value")) field = serializers.ListField(required=True, label=_("Fields")) class ConditionBranchSerializer(serializers.Serializer): id = serializers.CharField(required=True, label=_("Branch id")) type = serializers.CharField(required=True, label=_("Branch Type")) condition = serializers.CharField(required=True, label=_("Condition or|and")) conditions = ConditionSerializer(many=True) class ConditionNodeParamsSerializer(serializers.Serializer): branch = ConditionBranchSerializer(many=True) class IConditionNode(INode): def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ConditionNodeParamsSerializer type = 'condition-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] ================================================ FILE: apps/application/flow/step_node/condition_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:35 @desc: """ from .base_condition_node import BaseConditionNode ================================================ FILE: apps/application/flow/step_node/condition_node/impl/base_condition_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_condition_node.py @date:2024/6/7 11:29 @desc: """ from typing import List from application.flow.i_step_node import NodeResult from application.flow.compare import compare_handle_list from application.flow.step_node.condition_node.i_condition_node import IConditionNode class BaseConditionNode(IConditionNode): def save_context(self, details, workflow_manage): self.context['branch_id'] = details.get('branch_id') self.context['branch_name'] = details.get('branch_name') self.context['exception_message'] = details.get('err_message') def execute(self, **kwargs) -> NodeResult: branch_list = self.node_params_serializer.data['branch'] branch = self._execute(branch_list) r = NodeResult({'branch_id': branch.get('id'), 'branch_name': branch.get('type')}, {}) return r def _execute(self, branch_list: List): for branch in branch_list: if self.branch_assertion(branch): return branch def branch_assertion(self, branch): condition_list = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in branch.get('conditions')] condition = branch.get('condition') return all(condition_list) if condition == 'and' else any(condition_list) def assertion(self, field_list: List[str], compare: str, value): try: value = self.workflow_manage.generate_prompt(value) except Exception as e: pass field_value = None try: field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:]) except Exception as e: pass for compare_handler in compare_handle_list: if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value): return compare_handler.compare(field_value, compare, value) def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'branch_id': self.context.get('branch_id'), 'branch_name': self.context.get('branch_name'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/data_source_local_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/11 10:06 @desc: """ ================================================ FILE: apps/application/flow/step_node/data_source_local_node/i_data_source_local_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: i_data_source_local_node.py @date:2025/11/11 10:06 @desc: """ from abc import abstractmethod from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class DataSourceLocalNodeParamsSerializer(serializers.Serializer): file_type_list = serializers.ListField(child=serializers.CharField(label=('')), label='') file_size_limit = serializers.IntegerField(required=True, label=_("Number of uploaded files")) file_count_limit = serializers.IntegerField(required=True, label=_("Upload file size")) class IDataSourceLocalNode(INode): type = 'data-source-local-node' @staticmethod @abstractmethod def get_form_list(node): pass def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return DataSourceLocalNodeParamsSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, file_type_list, file_size_limit, file_count_limit, **kwargs) -> NodeResult: pass support = [WorkflowMode.KNOWLEDGE] ================================================ FILE: apps/application/flow/step_node/data_source_local_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/11 10:08 @desc: """ ================================================ FILE: apps/application/flow/step_node/data_source_local_node/impl/base_data_source_local_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_data_source_local_node.py @date:2025/11/11 10:30 @desc: """ from application.flow.i_step_node import NodeResult from application.flow.step_node.data_source_local_node.i_data_source_local_node import IDataSourceLocalNode from common import forms from common.forms import BaseForm class BaseDataSourceLocalNodeForm(BaseForm): api_key = forms.PasswordInputField('API Key', required=True) class BaseDataSourceLocalNode(IDataSourceLocalNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') @staticmethod def get_form_list(node): node_data = node.get('properties').get('node_data') return [{ 'field': 'file_list', 'input_type': 'LocalFileUpload', 'attrs': { 'file_count_limit': node_data.get('file_count_limit') or 10, 'file_size_limit': node_data.get('file_size_limit') or 100, 'file_type_list': node_data.get('file_type_list'), }, 'label': '', }] def execute(self, file_type_list, file_size_limit, file_count_limit, **kwargs) -> NodeResult: return NodeResult({'file_list': self.workflow_manage.params.get('data_source', {}).get('file_list')}, self.workflow_manage.params.get('knowledge_base') or {}) 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, 'file_list': self.context.get('file_list'), 'knowledge_base': self.workflow_params.get('knowledge_base'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/data_source_web_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/11/12 13:43 @desc: """ ================================================ FILE: apps/application/flow/step_node/data_source_web_node/i_data_source_web_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: i_data_source_web_node.py @date:2025/11/12 13:47 @desc: """ from abc import abstractmethod from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class IDataSourceWebNode(INode): type = 'data-source-web-node' support = [WorkflowMode.KNOWLEDGE] @staticmethod @abstractmethod def get_form_list(node): pass def _run(self): return self.execute(**self.flow_params_serializer.data) def execute(self, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/data_source_web_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py @date:2025/11/12 13:44 @desc: """ ================================================ FILE: apps/application/flow/step_node/data_source_web_node/impl/base_data_source_web_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: base_data_source_web_node.py @date:2025/11/12 13:47 @desc: """ import traceback from django.utils.translation import gettext_lazy as _ from application.flow.i_step_node import NodeResult from application.flow.step_node.data_source_web_node.i_data_source_web_node import IDataSourceWebNode from common import forms from common.forms import BaseForm from common.utils.fork import ForkManage, Fork, ChildLink from common.utils.logger import maxkb_logger class BaseDataSourceWebNodeForm(BaseForm): source_url = forms.TextInputField(_('Web source url'), required=True, attrs={ 'placeholder': _('Please enter the Web root address')}) selector = forms.TextInputField(_('Web knowledge selector'), required=False, attrs={ 'placeholder': _('The default is body, you can enter .classname/#idname/tagname')}) class InterruptedTaskException(Exception): def __init__(self, *args, **kwargs): # real signature unknown pass def get_collect_handler(workflow_manage): results = [] def handler(child_link: ChildLink, response: Fork.Response): if response.status == 200: try: document_name = child_link.tag.text if child_link.tag is not None and len( child_link.tag.text.strip()) > 0 else child_link.url results.append({ "name": document_name.strip(), "content": response.content, }) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') if workflow_manage.is_the_task_interrupted(): raise InterruptedTaskException('Task interrupted') return handler, results class BaseDataSourceWebNode(IDataSourceWebNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') @staticmethod def get_form_list(node): return BaseDataSourceWebNodeForm().to_form_list() def execute(self, **kwargs) -> NodeResult: BaseDataSourceWebNodeForm().valid_form(self.workflow_params.get("data_source")) data_source = self.workflow_params.get("data_source") node_id = data_source.get("node_id") source_url = data_source.get("source_url") selector = data_source.get("selector") or "body" collect_handler, document_list = get_collect_handler(self.workflow_manage) try: ForkManage(source_url, selector.split(" ") if selector is not None else []).fork(3, set(), collect_handler) return NodeResult({'document_list': document_list, 'source_url': source_url, 'selector': selector}, self.workflow_manage.params.get('knowledge_base') or {}) except Exception as e: if isinstance(e, InterruptedTaskException): return NodeResult({'document_list': document_list, 'source_url': source_url, 'selector': selector}, self.workflow_manage.params.get('knowledge_base') or {}) maxkb_logger.error(_('data source web node:{node_id} error{error}{traceback}').format( knowledge_id=node_id, error=str(e), traceback=traceback.format_exc())) 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, 'input_params': {"source_url": self.context.get("source_url"), "selector": self.context.get('selector')}, 'output_params': self.context.get('document_list'), 'knowledge_base': self.workflow_params.get('knowledge_base'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/direct_reply_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 17:50 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/direct_reply_node/i_reply_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_reply_node.py @date:2024/6/11 16:25 @desc: """ from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ class ReplyNodeParamsSerializer(serializers.Serializer): reply_type = serializers.CharField(required=True, label=_("Response Type")) fields = serializers.ListField(required=False, label=_("Reference Field")) content = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Direct answer content")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if self.data.get('reply_type') == 'referencing': if 'fields' not in self.data: raise AppApiException(500, _("Reference field cannot be empty")) if len(self.data.get('fields')) < 2: raise AppApiException(500, _("Reference field error")) else: if 'content' not in self.data or self.data.get('content') is None: raise AppApiException(500, _("Content cannot be empty")) class IReplyNode(INode): type = 'reply-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ReplyNodeParamsSerializer def _run(self): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'stream': True}) else: return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/direct_reply_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 17:49 @desc: """ from .base_reply_node import * ================================================ FILE: apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_reply_node.py @date:2024/6/11 17:25 @desc: """ from typing import List from application.flow.i_step_node import NodeResult from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode class BaseReplyNode(IReplyNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult: if reply_type == 'referencing': result = self.get_reference_content(fields) else: result = self.generate_reply_content(content) return NodeResult({'answer': result}, {}) def generate_reply_content(self, prompt): return self.workflow_manage.generate_prompt(prompt) 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, 'answer': self.context.get('answer'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/document_extract_node/__init__.py ================================================ from .impl import * ================================================ FILE: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class DocumentExtractNodeSerializer(serializers.Serializer): document_list = serializers.ListField(required=False, label=_("document")) class IDocumentExtractNode(INode): type = 'document-extract-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return DocumentExtractNodeSerializer def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('document_list')[0], self.node_params_serializer.data.get('document_list')[1:]) return self.execute(document=res, **self.flow_params_serializer.data) def execute(self, document, chat_id=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/document_extract_node/impl/__init__.py ================================================ from .base_document_extract_node import BaseDocumentExtractNode ================================================ FILE: apps/application/flow/step_node/document_extract_node/impl/base_document_extract_node.py ================================================ # coding=utf-8 import ast import io import uuid_utils.compat as uuid from django.db.models import QuerySet from application.flow.i_step_node import NodeResult from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode from knowledge.models import File, FileSourceType from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle splitter = '\n`-----------------------------------`\n' class BaseDocumentExtractNode(IDocumentExtractNode): def save_context(self, details, workflow_manage): self.context['content'] = details.get('content') self.context['exception_message'] = details.get('err_message') def execute(self, document, chat_id=None, **kwargs): get_buffer = FileBufferHandle().get_buffer self.context['document_list'] = document content = [] if document is None or not isinstance(document, list): return NodeResult({'content': '', 'document_list': []}, {}) # 安全获取 application application_id = None if (self.workflow_manage and self.workflow_manage.work_flow_post_handler and self.workflow_manage.work_flow_post_handler.chat_info): application_id = self.workflow_manage.work_flow_post_handler.chat_info.application.id knowledge_id = self.workflow_params.get('knowledge_id') # doc文件中的图片保存 def save_image(image_list): for image in image_list: meta = { 'debug': False if (application_id or knowledge_id) else True, 'chat_id': chat_id, 'application_id': str(application_id) if application_id else None, 'knowledge_id': str(knowledge_id) if knowledge_id else None, 'file_id': str(image.id) } file_bytes = image.meta.pop('content') new_file = File( id=meta['file_id'], file_name=image.file_name, file_size=len(file_bytes), source_type=FileSourceType.APPLICATION.value if meta[ 'application_id'] else FileSourceType.KNOWLEDGE.value, source_id=meta['application_id'] if meta['application_id'] else meta['knowledge_id'], meta=meta ) if not QuerySet(File).filter(id=new_file.id).exists(): new_file.save(file_bytes) document_list = [] for doc in document: file = QuerySet(File).filter(id=doc['file_id']).first() buffer = io.BytesIO(file.get_bytes()) buffer.name = doc['name'] # this is the important line for split_handle in (parse_table_handle_list + split_handles): if split_handle.support(buffer, get_buffer): # 回到文件头 buffer.seek(0) file_content = split_handle.get_content(buffer, save_image) content.append('### ' + doc['name'] + '\n' + file_content) document_list.append({'id': str(file.id), 'name': doc['name'], 'content': file_content}) break return NodeResult({'content': splitter.join(content), 'document_list': document_list}, {}) def get_details(self, index: int, **kwargs): content = self.context.get('content', '').split(splitter) # 不保存content全部内容,因为content内容可能会很大 return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'type': self.node.type, 'content': [file_content[:500] for file_content in content], 'status': self.status, 'err_message': self.err_message, 'document_list': self.context.get('document_list'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/document_split_node/__init__.py ================================================ from .impl import * ================================================ FILE: apps/application/flow/step_node/document_split_node/i_document_split_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class DocumentSplitNodeSerializer(serializers.Serializer): document_list = serializers.ListField(required=False, label=_("document list")) split_strategy = serializers.ChoiceField( choices=['auto', 'custom', 'qa'], required=False, label=_("split strategy"), default='auto' ) paragraph_title_relate_problem_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("paragraph title relate problem type"), default='custom' ) paragraph_title_relate_problem = serializers.BooleanField( required=False, label=_("paragraph title relate problem"), default=False ) paragraph_title_relate_problem_reference = serializers.ListField( required=False, label=_("paragraph title relate problem reference"), child=serializers.CharField(), default=[] ) document_name_relate_problem_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("document name relate problem type"), default='custom' ) document_name_relate_problem = serializers.BooleanField( required=False, label=_("document name relate problem"), default=False ) document_name_relate_problem_reference = serializers.ListField( required=False, label=_("document name relate problem reference"), child=serializers.CharField(), default=[] ) limit = serializers.IntegerField(required=False, label=_("limit"), default=4096) limit_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("document name relate problem type"), default='custom' ) limit_reference = serializers.ListField( required=False, label=_("limit reference"), child=serializers.CharField(), default=[] ) chunk_size = serializers.IntegerField(required=False, label=_("chunk size"), default=256) chunk_size_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("chunk size type"), default='custom' ) chunk_size_reference = serializers.ListField( required=False, label=_("chunk size reference"), child=serializers.CharField(), default=[] ) patterns = serializers.ListField( required=False, label=_("patterns"), child=serializers.CharField(), default=[] ) patterns_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("patterns type"), default='custom' ) patterns_reference = serializers.ListField( required=False, label=_("patterns reference"), child=serializers.CharField(), default=[] ) with_filter = serializers.BooleanField( required=False, label=_("with filter"), default=False ) with_filter_type = serializers.ChoiceField( choices=['custom', 'referencing'], required=False, label=_("with filter type"), default='custom' ) with_filter_reference = serializers.ListField( required=False, label=_("with filter reference"), child=serializers.CharField(), default=[] ) class IDocumentSplitNode(INode): type = 'document-split-node' support = [ WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP ] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return DocumentSplitNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, document_list, knowledge_id, split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference, limit, limit_type, limit_reference, chunk_size, chunk_size_type, chunk_size_reference, patterns, patterns_type, patterns_reference, with_filter, with_filter_type, with_filter_reference, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/document_split_node/impl/__init__.py ================================================ from .base_document_split_node import BaseDocumentSplitNode ================================================ FILE: apps/application/flow/step_node/document_split_node/impl/base_document_split_node.py ================================================ # coding=utf-8 import io import mimetypes from typing import List from django.core.files.uploadedfile import InMemoryUploadedFile from application.flow.i_step_node import NodeResult from application.flow.step_node.document_split_node.i_document_split_node import IDocumentSplitNode from common.chunk import text_to_chunk from knowledge.serializers.document import default_split_handle, FileBufferHandle, md_qa_split_handle def bytes_to_uploaded_file(file_bytes, file_name="file.txt"): if file_name.startswith("http"): file_name = "file.txt" content_type, _ = mimetypes.guess_type(file_name) if content_type is None: # 如果未能识别,设置为默认的二进制文件类型 content_type = "application/octet-stream" # 创建一个内存中的字节流对象 file_stream = io.BytesIO(file_bytes) # 获取文件大小 file_size = len(file_bytes) # 创建 InMemoryUploadedFile 对象 uploaded_file = InMemoryUploadedFile( file=file_stream, field_name=None, name=file_name, content_type=content_type, size=file_size, charset=None, ) return uploaded_file class BaseDocumentSplitNode(IDocumentSplitNode): def save_context(self, details, workflow_manage): self.context['content'] = details.get('content') self.context['exception_message'] = details.get('err_message') def get_reference_content(self, fields: List[str]): return self.workflow_manage.get_reference_field(fields[0], fields[1:]) def execute(self, document_list, knowledge_id, split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference, limit, limit_type, limit_reference, chunk_size, chunk_size_type, chunk_size_reference, patterns, patterns_type, patterns_reference, with_filter, with_filter_type, with_filter_reference, **kwargs) -> NodeResult: self.context['knowledge_id'] = knowledge_id file_list = self.get_reference_content(document_list) # 处理引用类型的参数 if patterns_type == 'referencing': patterns = self.get_reference_content(patterns_reference) if limit_type == 'referencing': limit = self.get_reference_content(limit_reference) if chunk_size_type == 'referencing': chunk_size = self.get_reference_content(chunk_size_reference) if with_filter_type == 'referencing': with_filter = self.get_reference_content(with_filter_reference) paragraph_list = [] for doc in file_list: get_buffer = FileBufferHandle().get_buffer file_mem = bytes_to_uploaded_file(doc['content'].encode('utf-8'), doc['name']) if split_strategy == 'qa': result = md_qa_split_handle.handle(file_mem, get_buffer, self._save_image) else: result = default_split_handle.handle(file_mem, patterns, with_filter, limit, get_buffer, self._save_image) # 统一处理结果为列表 results = result if isinstance(result, list) else [result] for item in results: self._process_split_result( item, knowledge_id, doc.get('id'), doc.get('name'), split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference, chunk_size ) paragraph_list += results self.context['paragraph_list'] = paragraph_list self.context['document_list'] = file_list self.context['limit'] = limit self.context['chunk_size'] = chunk_size self.context['with_filter'] = with_filter self.context['patterns'] = patterns self.context['split_strategy'] = split_strategy return NodeResult({'paragraph_list': paragraph_list}, {}) def _save_image(self, image_list): pass def _process_split_result( self, item, knowledge_id, source_file_id, file_name, split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference, chunk_size ): """处理文档分割结果""" item['meta'] = { 'knowledge_id': knowledge_id, 'source_file_id': source_file_id, 'source_url': file_name, } if item.get('name', 'file.txt') == 'file.txt': item['name'] = file_name item['source_file_id'] = source_file_id item['paragraphs'] = item.pop('content', item.get('paragraphs', [])) for paragraph in item['paragraphs']: paragraph['problem_list'] = self._generate_problem_list( paragraph, file_name, split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference ) paragraph['is_active'] = True paragraph['chunks'] = text_to_chunk(paragraph['content'], chunk_size) def _generate_problem_list( self, paragraph, document_name, split_strategy, paragraph_title_relate_problem_type, paragraph_title_relate_problem, paragraph_title_relate_problem_reference, document_name_relate_problem_type, document_name_relate_problem, document_name_relate_problem_reference ): if paragraph_title_relate_problem_type == 'referencing': paragraph_title_relate_problem = self.get_reference_content(paragraph_title_relate_problem_reference) if document_name_relate_problem_type == 'referencing': document_name_relate_problem = self.get_reference_content(document_name_relate_problem_reference) problem_list = [ item for p in paragraph.get('problem_list', []) for item in p.get('content', '').split('
') if item.strip() ] if split_strategy == 'auto': if paragraph_title_relate_problem and paragraph.get('title'): problem_list.append(paragraph.get('title')) if document_name_relate_problem and document_name: problem_list.append(document_name) elif split_strategy == 'custom': if paragraph_title_relate_problem and paragraph.get('title'): problem_list.append(paragraph.get('title')) if document_name_relate_problem and document_name: problem_list.append(document_name) elif split_strategy == 'qa': if document_name_relate_problem and document_name: problem_list.append(document_name) return list(set(problem_list)) def get_details(self, index: int, **kwargs): paragraph_list = self.context.get('paragraph_list', []) # 每个文档保留前5个分段 limited_paragraph_list = [] for doc in paragraph_list: if doc.get('paragraphs'): doc_copy = doc.copy() doc_copy['paragraphs'] = doc['paragraphs'][:5] limited_paragraph_list.append(doc_copy) else: limited_paragraph_list.append(doc) paragraph_list = limited_paragraph_list return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'paragraph_list': paragraph_list, 'limit': self.context.get('limit'), 'chunk_size': self.context.get('chunk_size'), 'with_filter': self.context.get('with_filter'), 'patterns': self.context.get('patterns'), 'split_strategy': self.context.get('split_strategy'), 'enableException': self.node.properties.get('enableException'), # 'document_list': self.context.get('document_list', []), } ================================================ FILE: apps/application/flow/step_node/form_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/11/4 14:48 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/form_node/i_form_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_form_node.py @date:2024/11/4 14:48 @desc: """ from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class FormNodeParamsSerializer(serializers.Serializer): form_field_list = serializers.ListField(required=True, label=_("Form Configuration")) form_content_format = serializers.CharField(required=True, label=_('Form output content')) form_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data")) class IFormNode(INode): type = 'form-node' view_type = 'single_view' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FormNodeParamsSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/form_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/11/4 14:49 @desc: """ from .base_form_node import BaseFormNode ================================================ FILE: apps/application/flow/step_node/form_node/impl/base_form_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_form_node.py @date:2024/11/4 14:52 @desc: """ import json import time from typing import Dict, List from langchain_core.prompts import PromptTemplate from application.flow.common import Answer from application.flow.i_step_node import NodeResult from application.flow.step_node.form_node.i_form_node import IFormNode multi_select_list = [ 'MultiSelect', 'MultiRow' ] def get_default_option(option_list, _type, value_field): try: if option_list is not None and isinstance(option_list, list) and len(option_list) > 0: default_value_list = [o.get(value_field) for o in option_list if o.get('default')] if len(default_value_list) == 0: return [option_list[0].get( value_field)] if multi_select_list.__contains__(_type) else option_list[0].get( value_field) else: if multi_select_list.__contains__(_type): return default_value_list else: return default_value_list[0] except Exception as _: pass return [] def write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable is not None: for key in step_variable: node.context[key] = step_variable[key] if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable: result = step_variable['result'] yield result node.answer_text = result node.context['run_time'] = time.time() - node.context['start_time'] def generate_prompt(workflow_manage, _value): try: return workflow_manage.generate_prompt(_value) except Exception as e: return _value class BaseFormNode(IFormNode): def save_context(self, details, workflow_manage): form_data = details.get('form_data', None) self.context['result'] = details.get('result') self.context['form_content_format'] = details.get('form_content_format') self.context['form_field_list'] = details.get('form_field_list') self.context['run_time'] = details.get('run_time') self.context['start_time'] = details.get('start_time') self.context['form_data'] = form_data self.context['is_submit'] = details.get('is_submit') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('result') if form_data is not None: for key in form_data: self.context[key] = form_data[key] def reset_field(self, field): reset_field = ['field', 'label', 'default_value'] for f in reset_field: _value = field[f] if _value is None: continue if isinstance(_value, str): field[f] = generate_prompt(self.workflow_manage, _value) elif f == 'label': _label_value = _value.get('label') _value['label'] = generate_prompt(self.workflow_manage, _label_value) tooltip = _value.get('attrs').get('tooltip') if tooltip is not None: _value.get('attrs')['tooltip'] = generate_prompt(self.workflow_manage, tooltip) if ['SingleSelect', 'MultiSelect', 'RadioCard', 'RadioRow', 'MultiRow'].__contains__(field.get('input_type')): if field.get('assignment_method') == 'ref_variables': option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0], field.get('option_list')[1:]) option_list = option_list if isinstance(option_list, list) else [] field['option_list'] = option_list field['default_value'] = get_default_option(option_list, field.get('input_type'), field.get('value_field')) if ['JsonInput'].__contains__(field.get('input_type')): if field.get('default_value_assignment_method') == 'ref_variables': field['default_value'] = self.workflow_manage.get_reference_field(field.get('default_value')[0], field.get('default_value')[1:]) return field def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult: if form_data is not None: self.context['is_submit'] = True self.context['form_data'] = form_data for key in form_data: self.context[key] = form_data.get(key) else: self.context['is_submit'] = False form_field_list = [self.reset_field(field) for field in form_field_list] form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "is_submit": self.context.get("is_submit", False)} form = f'{json.dumps(form_setting, ensure_ascii=False)}' context = self.workflow_manage.get_workflow_content() form_content_format = self.workflow_manage.reset_prompt(form_content_format) prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id, chat_record_id=self.flow_params_serializer.data.get("chat_record_id"), form_field_list=form_field_list) return NodeResult( {'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {}, _write_context=write_context) def get_answer_list(self) -> List[Answer] | None: form_content_format = self.context.get('form_content_format') form_field_list = self.context.get('form_field_list') form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), 'form_data': self.context.get('form_data', {}), "is_submit": self.context.get("is_submit", False)} form = f'{json.dumps(form_setting, ensure_ascii=False)}' context = self.workflow_manage.get_workflow_content() form_content_format = self.workflow_manage.reset_prompt(form_content_format) prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id, chat_record_id=self.flow_params_serializer.data.get("chat_record_id"), form_field_list=form_field_list) return [ Answer(value, self.view_type, self.runtime_node_id, self.workflow_params.get('chat_record_id') or '', None, self.runtime_node_id, '')] def get_details(self, index: int, **kwargs): form_content_format = self.context.get('form_content_format') form_field_list = self.context.get('form_field_list') form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), 'form_data': self.context.get('form_data', {}), "is_submit": self.context.get("is_submit", False)} form = f'{json.dumps(form_setting, ensure_ascii=False)}' context = self.workflow_manage.get_workflow_content() form_content_format = self.workflow_manage.reset_prompt(form_content_format) prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id, chat_record_id=self.flow_params_serializer.data.get("chat_record_id"), form_field_list=form_field_list) return { 'name': self.node.properties.get('stepName'), "index": index, "result": value, "form_content_format": self.context.get('form_content_format'), "form_field_list": self.context.get('form_field_list'), 'form_data': self.context.get('form_data'), 'start_time': self.context.get('start_time'), 'is_submit': self.context.get('is_submit'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/image_generate_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class ImageGenerateNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) prompt = serializers.CharField(required=True, label=_("Prompt word (positive)")) negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"), allow_null=True, allow_blank=True, ) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=False, default=0, label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=False, default='NODE', label=_("Conversation storage type")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model parameter settings")) class IImageGenerateNode(INode): type = 'image-generate-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageGenerateNodeSerializer def _run(self): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/image_generate_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_image_generate_node import BaseImageGenerateNode ================================================ FILE: apps/application/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py ================================================ # coding=utf-8 from functools import reduce from typing import List import requests from langchain_core.messages import BaseMessage, HumanMessage, AIMessage from application.flow.common import WorkflowMode from application.flow.i_step_node import NodeResult from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode from common.utils.common import bytes_to_uploaded_file from knowledge.models import FileSourceType from oss.serializers.file import FileSerializer from models_provider.tools import get_model_instance_by_model_workspace_id class BaseImageGenerateNode(IImageGenerateNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['question'] = details.get('question') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') tti_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question message_list = self.generate_message_list(question, history_message) self.context['message_list'] = message_list self.context['dialogue_type'] = dialogue_type self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt) image_urls = tti_model.generate_image(question, negative_prompt) # 保存图片 file_urls = [] for image_url in image_urls: file_name = 'generated_image.png' if isinstance(image_url, str): if image_url.startswith('http'): # HTTP URL 情况 image_url = requests.get(image_url).content elif image_url.startswith('data:image'): # Data URL 格式 (data:image/png;base64,...) import base64 header, encoded = image_url.split(',', 1) image_url = base64.b64decode(encoded) else: import base64 image_url = base64.b64decode(image_url) file = bytes_to_uploaded_file(image_url, file_name) file_url = self.upload_file(file) file_urls.append(file_url) self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls] answer = ' '.join([f"![Image]({path})" for path in file_urls]) return NodeResult({'answer': answer, 'chat_model': tti_model, 'message_list': message_list, 'image': [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls], 'history_message': history_message, 'question': question}, {}) def generate_history_ai_message(self, chat_record): for val in chat_record.details.values(): if self.node.id == val['node_id'] and 'image_list' in val: if val['dialogue_type'] == 'WORKFLOW': return chat_record.get_ai_message() image_list = val['image_list'] return AIMessage(content=[ *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list] ]) return chat_record.get_ai_message() def get_history_message(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_human_message(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'image_list' in data: image_list = data['image_list'] if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) return HumanMessage(content=data['question']) return HumanMessage(content=chat_record.problem_text) def generate_prompt_question(self, prompt): return self.workflow_manage.generate_prompt(prompt) def generate_message_list(self, question: str, history_message): return [ *history_message, question ] def upload_file(self, file): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.upload_knowledge_file(file) return self.upload_application_file(file) def upload_knowledge_file(self, file): knowledge_id = self.workflow_params.get('knowledge_id') meta = { 'debug': False, 'knowledge_id': knowledge_id, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': knowledge_id, 'source_type': FileSourceType.KNOWLEDGE.value }).upload() return file_url def upload_application_file(self, file): application = self.workflow_manage.work_flow_post_handler.chat_info.application chat_id = self.workflow_params.get('chat_id') meta = { 'debug': False if application.id else True, 'chat_id': chat_id, 'application_id': str(application.id) if application.id else None, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': meta['application_id'], 'source_type': FileSourceType.APPLICATION.value }).upload() return file_url @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'image_list': self.context.get('image_list'), 'dialogue_type': self.context.get('dialogue_type'), 'negative_prompt': self.context.get('negative_prompt'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/image_to_video_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class ImageToVideoNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) prompt = serializers.CharField(required=True, label=_("Prompt word (positive)")) negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"), allow_null=True, allow_blank=True, ) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=False, default=0, label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=False, default='NODE', label=_("Conversation storage type")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model parameter settings")) first_frame_url = serializers.ListField(required=True, label=_("First frame url")) last_frame_url = serializers.ListField(required=False, label=_("Last frame url")) class IImageToVideoNode(INode): type = 'image-to-video-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageToVideoNodeSerializer def _run(self): first_frame_url = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('first_frame_url')[0], self.node_params_serializer.data.get('first_frame_url')[1:]) if first_frame_url is []: raise ValueError( _("First frame url cannot be empty")) last_frame_url = None if self.node_params_serializer.data.get('last_frame_url') is not None and self.node_params_serializer.data.get( 'last_frame_url') != []: last_frame_url = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('last_frame_url')[0], self.node_params_serializer.data.get('last_frame_url')[1:]) node_params_data = {k: v for k, v in self.node_params_serializer.data.items() if k not in ['first_frame_url', 'last_frame_url']} if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, **node_params_data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, **node_params_data, **self.flow_params_serializer.data) def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, first_frame_url, last_frame_url, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/image_to_video_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_image_to_video_node import BaseImageToVideoNode ================================================ FILE: apps/application/flow/step_node/image_to_video_step_node/impl/base_image_to_video_node.py ================================================ # coding=utf-8 import base64 from functools import reduce from typing import List import requests from django.db.models import QuerySet from langchain_core.messages import BaseMessage, HumanMessage, AIMessage from application.flow.common import WorkflowMode from application.flow.i_step_node import NodeResult from application.flow.step_node.image_to_video_step_node.i_image_to_video_node import IImageToVideoNode from common.utils.common import bytes_to_uploaded_file from knowledge.models import FileSourceType, File from oss.serializers.file import FileSerializer, mime_types from models_provider.tools import get_model_instance_by_model_workspace_id from django.utils.translation import gettext class BaseImageToVideoNode(IImageToVideoNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['question'] = details.get('question') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, first_frame_url, last_frame_url=None, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') ttv_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question message_list = self.generate_message_list(question, history_message) self.context['message_list'] = message_list self.context['dialogue_type'] = dialogue_type self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt) self.context['first_frame_url'] = first_frame_url self.context['last_frame_url'] = last_frame_url # 处理首尾帧图片 这块可以是url 也可以是file_id 如果是url 可以直接传递给模型 如果是file_id 需要传base64 # 判断是不是 url first_frame_url = self.get_file_base64(first_frame_url) last_frame_url = self.get_file_base64(last_frame_url) video_urls = ttv_model.generate_video(question, negative_prompt, first_frame_url, last_frame_url) # 保存图片 if video_urls is None or video_urls == '': return NodeResult({'answer': gettext('Failed to generate video')}, {}) file_name = 'generated_video.mp4' if isinstance(video_urls, str) and video_urls.startswith('http'): video_urls = requests.get(video_urls).content file = bytes_to_uploaded_file(video_urls, file_name) file_url = self.upload_file(file) video_label = f'' video_list = [{'file_id': file_url.split('/')[-1], 'file_name': file_name, 'url': file_url}] return NodeResult({'answer': video_label, 'chat_model': ttv_model, 'message_list': message_list, 'video': video_list, 'history_message': history_message, 'question': question}, {}) def get_file_base64(self, image_url): try: if isinstance(image_url, list): image_url = image_url[0].get('file_id') if 'file_id' in image_url[0] else image_url[0].get('url') if isinstance(image_url, str) and not image_url.startswith('http'): file = QuerySet(File).filter(id=image_url).first() file_bytes = file.get_bytes() # 如果我不知道content_type 可以用 magic 库去检测 file_type = file.file_name.split(".")[-1].lower() content_type = mime_types.get(file_type, 'application/octet-stream') encoded_bytes = base64.b64encode(file_bytes) return f'data:{content_type};base64,{encoded_bytes.decode()}' return image_url except Exception as e: raise ValueError( gettext("Failed to obtain the image")) def upload_file(self, file): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.upload_knowledge_file(file) return self.upload_application_file(file) def upload_knowledge_file(self, file): knowledge_id = self.workflow_params.get('knowledge_id') meta = { 'debug': False, 'knowledge_id': knowledge_id } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': knowledge_id, 'source_type': FileSourceType.KNOWLEDGE.value }).upload() return file_url def upload_application_file(self, file): application = self.workflow_manage.work_flow_post_handler.chat_info.application chat_id = self.workflow_params.get('chat_id') meta = { 'debug': False if application.id else True, 'chat_id': chat_id, 'application_id': str(application.id) if application.id else None, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': meta['application_id'], 'source_type': FileSourceType.APPLICATION.value }).upload() return file_url def generate_history_ai_message(self, chat_record): for val in chat_record.details.values(): if self.node.id == val['node_id'] and 'image_list' in val: if val['dialogue_type'] == 'WORKFLOW': return chat_record.get_ai_message() image_list = val['image_list'] return AIMessage(content=[ *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list] ]) return chat_record.get_ai_message() def get_history_message(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_human_message(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'image_list' in data: image_list = data['image_list'] if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) return HumanMessage(content=data['question']) return HumanMessage(content=chat_record.problem_text) def generate_prompt_question(self, prompt): return self.workflow_manage.generate_prompt(prompt) def generate_message_list(self, question: str, history_message): return [ *history_message, question ] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'first_frame_url': self.context.get('first_frame_url'), 'last_frame_url': self.context.get('last_frame_url'), 'dialogue_type': self.context.get('dialogue_type'), 'negative_prompt': self.context.get('negative_prompt'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/image_understand_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py ================================================ # coding=utf-8 from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class ImageUnderstandNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Role Setting")) prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) image_list = serializers.ListField(required=False, label=_("picture")) model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model parameter settings")) class IImageUnderstandNode(INode): type = 'image-understand-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ImageUnderstandNodeSerializer def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('image_list')[0], self.node_params_serializer.data.get('image_list')[1:]) if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_record_id': None}) else: return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, model_params_setting, chat_record_id, image, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/image_understand_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_image_understand_node import BaseImageUnderstandNode ================================================ FILE: apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py ================================================ # coding=utf-8 import base64 import time from functools import reduce from imghdr import what from typing import List, Dict from django.db.models import QuerySet from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage from application.flow.i_step_node import NodeResult, INode from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode from knowledge.models import File from models_provider.tools import get_model_instance_by_model_workspace_id def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): chat_model = node_variable.get('chat_model') message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0 answer_tokens = chat_model.get_num_tokens(answer) node.context['message_tokens'] = message_tokens node.context['answer_tokens'] = answer_tokens node.context['answer'] = answer node.context['history_message'] = node_variable['history_message'] node.context['question'] = node_variable['question'] node.context['run_time'] = time.time() - node.context['start_time'] if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = '' for chunk in response: if isinstance(chunk.content, list): for chunk_item in chunk.content: text = chunk_item.get("text", "") answer += text yield text else: text = chunk.content or "" answer += text yield text _write_context(node_variable, workflow_variable, node, workflow, answer) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = response.content _write_context(node_variable, workflow_variable, node, workflow, answer) def file_id_to_base64(file_id: str): file = QuerySet(File).filter(id=file_id).first() file_bytes = file.get_bytes() base64_image = base64.b64encode(file_bytes).decode("utf-8") return [base64_image, what(None, file_bytes)] class BaseImageUnderstandNode(IImageUnderstandNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['question'] = details.get('question') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, model_params_setting, chat_record_id, image, **kwargs) -> NodeResult: # 处理不正确的参数 workspace_id = self.workflow_manage.get_body().get('workspace_id') image_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) # 执行详情中的历史消息不需要图片内容 history_message = self.get_history_message_for_details(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question.content # 生成消息列表, 真实的history_message message_list = self.generate_message_list(image_model, system, prompt, self.get_history_message(history_chat_record, dialogue_number), image) self.context['message_list'] = message_list self.generate_context_image(image) self.context['dialogue_type'] = dialogue_type if stream: r = image_model.stream(message_list) return NodeResult({'result': r, 'chat_model': image_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context_stream) else: r = image_model.invoke(message_list) return NodeResult({'result': r, 'chat_model': image_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context) def generate_context_image(self, image): if isinstance(image, str) and image.startswith('http'): self.context['image_list'] = [{'url': image}] elif image is not None and len(image) > 0: self.context['image_list'] = image def get_history_message_for_details(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message_for_details(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_ai_message(self, chat_record): for val in chat_record.details.values(): if self.node.id == val['node_id'] and 'image_list' in val: if val['dialogue_type'] == 'WORKFLOW': return chat_record.get_ai_message() return AIMessage(content=val['answer']) return chat_record.get_ai_message() def generate_history_human_message_for_details(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'image_list' in data: image_list = data['image_list'] if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) file_id_list = [] url_list = [] for image in image_list: if 'file_id' in image: file_id_list.append(image.get('file_id')) elif 'url' in image: url_list.append(image.get('url')) return HumanMessage(content=[ {'type': 'text', 'text': data['question']}, *[{'type': 'image_url', 'image_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list], *[{'type': 'image_url', 'image_url': {'url': url}} for url in url_list] ]) return HumanMessage(content=chat_record.problem_text) def get_history_message(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_human_message(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'image_list' in data: image_list = data['image_list'] if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) file_id_list = [] url_list = [] for image in image_list: if 'file_id' in image: file_id_list.append(image.get('file_id')) elif 'url' in image: url_list.append(image.get('url')) image_base64_list = [file_id_to_base64(file_id) for file_id in file_id_list] return HumanMessage( content=[ {'type': 'text', 'text': data['question']}, *[{'type': 'image_url', 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for base64_image in image_base64_list], *[{'type': 'image_url', 'image_url': url} for url in url_list] ]) return HumanMessage(content=chat_record.problem_text) def generate_prompt_question(self, prompt): return HumanMessage(self.workflow_manage.generate_prompt(prompt)) def _process_images(self, image): """ 处理图像数据,转换为模型可识别的格式 """ images = [] if isinstance(image, str) and image.startswith('http'): images.append({'type': 'image_url', 'image_url': {'url': image}}) elif image is not None and len(image) > 0: for img in image: if 'file_id' in img: file_id = img['file_id'] file = QuerySet(File).filter(id=file_id).first() image_bytes = file.get_bytes() base64_image = base64.b64encode(image_bytes).decode("utf-8") image_format = what(None, image_bytes) images.append( {'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}}) elif 'url' in img and img['url'].startswith('http'): images.append( {'type': 'image_url', 'image_url': {'url': img["url"]}}) return images def generate_message_list(self, image_model, system: str, prompt: str, history_message, image): prompt_text = self.workflow_manage.generate_prompt(prompt) images = self._process_images(image) if images: messages = [HumanMessage(content=[{'type': 'text', 'text': prompt_text}, *images])] else: messages = [HumanMessage(prompt_text)] if system is not None and len(system) > 0: return [ SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, *messages ] else: return [ *history_message, *messages ] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'system': self.node_params.get('system'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'image_list': self.context.get('image_list'), 'dialogue_type': self.context.get('dialogue_type'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/intent_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/intent_node/i_intent_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class IntentBranchSerializer(serializers.Serializer): id = serializers.CharField(required=True, label=_("Branch id")) content = serializers.CharField(required=True, label=_("content")) isOther = serializers.BooleanField(required=True, label=_("Branch Type")) class IntentNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) content_list = serializers.ListField(required=True, label=_("Text content")) dialogue_number = serializers.IntegerField(required=True, label= _("Number of multi-round conversations")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) branch = IntentBranchSerializer(many=True) class IIntentNode(INode): type = 'intent-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def save_context(self, details, workflow_manage): pass def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return IntentNodeSerializer def _run(self): question = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('content_list')[0], self.node_params_serializer.data.get('content_list')[1:], ) if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None, 'user_input': str(question)}) else: return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, user_input=str(question)) def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch, model_params_setting=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/intent_node/impl/__init__.py ================================================ from .base_intent_node import BaseIntentNode ================================================ FILE: apps/application/flow/step_node/intent_node/impl/base_intent_node.py ================================================ # coding=utf-8 import json import re import time from typing import List, Dict, Any from functools import reduce from django.db.models import QuerySet from langchain_core.messages import HumanMessage, SystemMessage from application.flow.i_step_node import INode, NodeResult from application.flow.step_node.intent_node.i_intent_node import IIntentNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential from .prompt_template import PROMPT_TEMPLATE def get_default_model_params_setting(model_id): model = QuerySet(Model).filter(id=model_id).first() credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = credential.get_model_params_setting_form( model.model_name).get_default_form_data() return model_params_setting def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): chat_model = node_variable.get('chat_model') message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list')) answer_tokens = chat_model.get_num_tokens(answer) node.context['message_tokens'] = message_tokens node.context['answer_tokens'] = answer_tokens node.context['answer'] = answer node.context['history_message'] = node_variable['history_message'] node.context['user_input'] = node_variable['user_input'] node.context['branch_id'] = node_variable.get('branch_id') node.context['reason'] = node_variable.get('reason') node.context['category'] = node_variable.get('category') node.context['run_time'] = time.time() - node.context['start_time'] def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): response = node_variable.get('result') answer = response.content _write_context(node_variable, workflow_variable, node, workflow, answer) class BaseIntentNode(IIntentNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') self.context['branch_id'] = details.get('branch_id') self.context['category'] = details.get('category') def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch, model_params_setting=None, **kwargs) -> NodeResult: # 设置默认模型参数 if model_params_setting is None: model_params_setting = get_default_model_params_setting(model_id) # 获取模型实例 workspace_id = self.workflow_manage.get_body().get('workspace_id') chat_model = get_model_instance_by_model_workspace_id( model_id, workspace_id, **model_params_setting ) # 获取历史对话 history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message # 保存问题到上下文 self.context['user_input'] = user_input # 构建分类提示词 prompt = self.build_classification_prompt(user_input, branch) # 生成消息列表 system = self.build_system_prompt() message_list = self.generate_message_list(system, prompt, history_message) self.context['message_list'] = message_list # 调用模型进行分类 try: r = chat_model.invoke(message_list) classification_result = r.content.strip() # 解析分类结果获取分支信息 matched_branch = self.parse_classification_result(classification_result, branch) # 返回结果 return NodeResult({ 'result': r, 'chat_model': chat_model, 'message_list': message_list, 'history_message': history_message, 'user_input': user_input, 'branch_id': matched_branch['id'], 'reason': self.parse_result_reason(r.content), 'category': matched_branch.get('content', matched_branch['id']) }, {}, _write_context=write_context) except Exception as e: # 错误处理:返回"其他"分支 other_branch = self.find_other_branch(branch) if other_branch: return NodeResult({ 'branch_id': other_branch['id'], 'category': other_branch.get('content', other_branch['id']), 'error': str(e) }, {}) else: raise Exception(f"error: {str(e)}") @staticmethod def get_history_message(history_chat_record, dialogue_number): """获取历史消息""" start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) for message in history_message: if isinstance(message.content, str): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message def build_system_prompt(self) -> str: """构建系统提示词""" return "你是一个专业的意图识别助手,请根据用户输入和意图选项,准确识别用户的真实意图。" def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> str: """构建分类提示词""" classification_list = [] other_branch = self.find_other_branch(branch) # 添加其他分支 if other_branch: classification_list.append({ "classificationId": 0, "content": other_branch.get('content') }) # 添加正常分支 classification_id = 1 for b in branch: if not b.get('isOther'): classification_list.append({ "classificationId": classification_id, "content": b['content'] }) classification_id += 1 return PROMPT_TEMPLATE.format( classification_list=classification_list, user_input=user_input ) def generate_message_list(self, system: str, prompt: str, history_message): """生成消息列表""" if system is None or len(system) == 0: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] def parse_classification_result(self, result: str, branch: List[Dict]) -> Dict[str, Any]: """解析分类结果""" other_branch = self.find_other_branch(branch) normal_intents = [ b for b in branch if not b.get('isOther') ] def get_branch_by_id(category_id: int): if category_id == 0: return other_branch elif 1 <= category_id <= len(normal_intents): return normal_intents[category_id - 1] return None try: result_json = json.loads(result) classification_id = result_json.get('classificationId') # 如果是 0 ,返回其他分支 matched_branch = get_branch_by_id(classification_id) if matched_branch: return matched_branch except Exception as e: # json 解析失败,re 提取 numbers = re.findall(r'"classificationId":\s*(\d+)', result) if numbers: classification_id = int(numbers[0]) matched_branch = get_branch_by_id(classification_id) if matched_branch: return matched_branch # 如果都解析失败,返回“other” return other_branch or (normal_intents[0] if normal_intents else {'id': 'unknown', 'content': 'unknown'}) def parse_result_reason(self, result: str): """解析分类的原因""" try: result_json = json.loads(result) return result_json.get('reason', '') except Exception as e: reason_patterns = [ r'"reason":\s*"([^"]*)"', # 标准格式 r'"reason":\s*"([^"]*)', # 缺少结束引号 r'"reason":\s*([^,}\n]*)', # 没有引号包围的内容 ] for pattern in reason_patterns: match = re.search(pattern, result, re.DOTALL) if match: reason = match.group(1).strip() # 清理可能的尾部字符 reason = re.sub(r'["\s]*$', '', reason) return reason return '' def find_other_branch(self, branch: List[Dict]) -> Dict[str, Any] | None: """查找其他分支""" for b in branch: if b.get('isOther'): return b return None def get_details(self, index: int, **kwargs): """获取节点执行详情""" return { 'name': self.node.properties.get('stepName'), 'index': index, 'run_time': self.context.get('run_time'), 'system': self.context.get('system'), 'history_message': [ {'content': message.content, 'role': message.type} for message in (self.context.get('history_message') or []) ], 'user_input': self.context.get('user_input'), 'answer': self.context.get('answer'), 'branch_id': self.context.get('branch_id'), 'category': self.context.get('category'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/intent_node/impl/prompt_template.py ================================================ PROMPT_TEMPLATE = """ # Role You are an intention classification expert, good at being able to judge which classification the user's input belongs to. ## Skills Skill 1: Clearly determine which of the following intention classifications the user's input belongs to. Intention classification list: {classification_list} Note: - Please determine the match only between the user's input content and the Intention classification list content, without judging or categorizing the match with the classification ID. - **When classifying, you must give higher weight to the context and intent continuity shown in the historical conversation. Do not rely solely on the literal meaning of the current input; instead, prioritize the most consistent classification with the previous dialogue flow.** ## User Input {user_input} ## Reply requirements - The answer must be returned in JSON format. - Strictly ensure that the output is in a valid JSON format. - Do not add prefix ```json or suffix ``` - The answer needs to include the following fields such as: {{ "classificationId": 0, "reason": "" }} ## Limit - Please do not reply in text.""" ================================================ FILE: apps/application/flow/step_node/knowledge_write_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/11/13 11:17 @desc: """ ================================================ FILE: apps/application/flow/step_node/knowledge_write_node/i_knowledge_write_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: i_knowledge_write_node.py @date:2025/11/13 11:19 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class KnowledgeWriteNodeParamSerializer(serializers.Serializer): document_list = serializers.ListField(required=True, child=serializers.CharField(required=True), allow_null=True, label=_('document list')) class IKnowledgeWriteNode(INode): def save_context(self, details, workflow_manage): pass def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return KnowledgeWriteNodeParamSerializer def _run(self): documents = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('document_list')[0], self.node_params_serializer.data.get('document_list')[1:], ) return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, documents=documents) def execute(self, documents, **kwargs) -> NodeResult: pass type = 'knowledge-write-node' support = [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP] ================================================ FILE: apps/application/flow/step_node/knowledge_write_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/11/13 11:18 @desc: """ ================================================ FILE: apps/application/flow/step_node/knowledge_write_node/impl/base_knowledge_write_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: base_knowledge_write_node.py @date:2025/11/13 11:19 @desc: """ from functools import reduce from typing import Dict, List, Any import uuid_utils.compat as uuid from django.db.models import QuerySet from django.db.models.aggregates import Max from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from application.flow.i_step_node import NodeResult from application.flow.step_node.knowledge_write_node.i_knowledge_write_node import IKnowledgeWriteNode from common.chunk import text_to_chunk from common.utils.common import bulk_create_in_batches, filter_special_character from knowledge.models import Document, KnowledgeType, Paragraph, File, FileSourceType, Problem, ProblemParagraphMapping, \ Tag, DocumentTag from knowledge.serializers.common import ProblemParagraphObject, ProblemParagraphManage from knowledge.serializers.document import DocumentSerializers class ParagraphInstanceSerializer(serializers.Serializer): content = serializers.CharField(required=True, label=_('content'), max_length=102400, min_length=1, allow_null=True, allow_blank=True) title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True, allow_blank=True) problem_list = serializers.ListField(required=False, child=serializers.CharField(required=False, allow_blank=True)) is_active = serializers.BooleanField(required=False, label=_('Is active')) chunks = serializers.ListField(required=False, child=serializers.CharField(required=True)) class TagInstanceSerializer(serializers.Serializer): key = serializers.CharField(required=True, max_length=64, label=_('Tag Key')) value = serializers.CharField(required=True, max_length=128, label=_('Tag Value')) class KnowledgeWriteParamSerializer(serializers.Serializer): name = serializers.CharField(required=True, label=_('document name'), max_length=128, min_length=1, source=_('document name')) meta = serializers.DictField(required=False) tags = serializers.ListField(required=False, label=_('Tags'), child=TagInstanceSerializer()) paragraphs = ParagraphInstanceSerializer(required=False, many=True, allow_null=True) source_file_id = serializers.UUIDField(required=False, allow_null=True) def convert_uuid_to_str(obj): if isinstance(obj, dict): return {k: convert_uuid_to_str(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_uuid_to_str(i) for i in obj] elif isinstance(obj, uuid.UUID): return str(obj) else: return obj def link_file(source_file_id, document_id): if source_file_id is None: return source_file = QuerySet(File).filter(id=source_file_id).first() if source_file: file_content = source_file.get_bytes() new_file = File( id=uuid.uuid7(), file_name=source_file.file_name, file_size=source_file.file_size, source_type=FileSourceType.DOCUMENT, source_id=document_id, # 更新为当前知识库ID meta=source_file.meta.copy() if source_file.meta else {} ) # 保存文件内容和元数据 new_file.save(file_content) def get_paragraph_problem_model(knowledge_id: str, document_id: str, instance: Dict): paragraph = Paragraph( id=uuid.uuid7(), document_id=document_id, content=filter_special_character(instance.get("content")), knowledge_id=knowledge_id, title=instance.get("title") if 'title' in instance else '', chunks=[filter_special_character(c) for c in (instance.get('chunks') if 'chunks' in instance else text_to_chunk( instance.get("content")))], ) problem_paragraph_object_list = [ProblemParagraphObject( knowledge_id, document_id, str(paragraph.id), problem ) for problem in (instance.get('problem_list') if 'problem_list' in instance else [])] return { 'paragraph': paragraph, 'problem_paragraph_object_list': problem_paragraph_object_list, } def get_paragraph_model(document_model, paragraph_list: List): knowledge_id = document_model.knowledge_id paragraph_model_dict_list = [ get_paragraph_problem_model(knowledge_id, document_model.id, paragraph) for paragraph in paragraph_list ] paragraph_model_list = [] problem_paragraph_object_list = [] for paragraphs in paragraph_model_dict_list: paragraph = paragraphs.get('paragraph') for problem_model in paragraphs.get('problem_paragraph_object_list'): problem_paragraph_object_list.append(problem_model) paragraph_model_list.append(paragraph) return { 'document': document_model, 'paragraph_model_list': paragraph_model_list, 'problem_paragraph_object_list': problem_paragraph_object_list, } def get_document_paragraph_model(knowledge_id: str, instance: Dict): source_meta = {'source_file_id': instance.get("source_file_id")} if instance.get("source_file_id") else {} meta = {**instance.get('meta'), **source_meta} if instance.get('meta') is not None else source_meta meta = {**convert_uuid_to_str(meta), 'allow_download': True} document_model = Document( **{ 'knowledge_id': knowledge_id, 'id': uuid.uuid7(), 'name': instance.get('name'), 'char_length': reduce( lambda x, y: x + y, [len(p.get('content')) for p in instance.get('paragraphs', [])], 0), 'meta': meta, 'type': instance.get('type') if instance.get('type') is not None else KnowledgeType.WORKFLOW } ) return get_paragraph_model( document_model, instance.get('paragraphs') if 'paragraphs' in instance else [] ) def save_knowledge_tags(knowledge_id: str, tags: List[Dict[str, Any]]): existed_tags_dict = { (key, value): str(tag_id) for key, value, tag_id in QuerySet(Tag).filter(knowledge_id=knowledge_id).values_list("key", "value", "id") } tag_model_list = [] new_tag_dict = {} for tag in tags: key = tag.get("key") value = tag.get("value") if (key, value) not in existed_tags_dict: tag_model = Tag( id=uuid.uuid7(), knowledge_id=knowledge_id, key=key, value=value ) tag_model_list.append(tag_model) new_tag_dict[(key, value)] = str(tag_model.id) if tag_model_list: Tag.objects.bulk_create(tag_model_list) all_tag_dict = {**existed_tags_dict, **new_tag_dict} return all_tag_dict, new_tag_dict def batch_add_document_tag(document_tag_map: Dict[str, List[str]]): """ 批量添加文档-标签关联 document_tag_map: {document_id: [tag_id1, tag_id2, ...]} """ all_document_ids = list(document_tag_map.keys()) all_tag_ids = list(set(tag_id for tag_ids in document_tag_map.values() for tag_id in tag_ids)) # 查询已存在的文档-标签关联 existed_relations = set( QuerySet(DocumentTag).filter( document_id__in=all_document_ids, tag_id__in=all_tag_ids ).values_list('document_id', 'tag_id') ) new_relations = [ DocumentTag( id=uuid.uuid7(), document_id=doc_id, tag_id=tag_id, ) for doc_id, tag_ids in document_tag_map.items() for tag_id in tag_ids if (doc_id, tag_id) not in existed_relations ] if new_relations: QuerySet(DocumentTag).bulk_create(new_relations) class BaseKnowledgeWriteNode(IKnowledgeWriteNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') def save(self, document_list): serializer = KnowledgeWriteParamSerializer(data=document_list, many=True) serializer.is_valid(raise_exception=True) document_list = serializer.data knowledge_id = self.workflow_params.get("knowledge_id") workspace_id = self.workflow_params.get("workspace_id") document_model_list = [] paragraph_model_list = [] problem_paragraph_object_list = [] # 所有标签 knowledge_tag_list = [] # 文档标签映射关系 document_tags_map = {} knowledge_tag_dict = {} for document in document_list: document_paragraph_dict_model = get_document_paragraph_model( knowledge_id, document ) document_instance = document_paragraph_dict_model.get('document') link_file(document.get("source_file_id"), document_instance.id) document_model_list.append(document_instance) # 收集标签 single_document_tag_list = document.get("tags", []) # 去重传入的标签 for tag in single_document_tag_list: tag_key = (tag['key'], tag['value']) if tag_key not in knowledge_tag_dict: knowledge_tag_dict[tag_key] = tag if single_document_tag_list: document_tags_map[str(document_instance.id)] = single_document_tag_list for paragraph in document_paragraph_dict_model.get("paragraph_model_list"): paragraph_model_list.append(paragraph) for problem_paragraph_object in document_paragraph_dict_model.get("problem_paragraph_object_list"): problem_paragraph_object_list.append(problem_paragraph_object) knowledge_tag_list = list(knowledge_tag_dict.values()) # 保存所有文档中含有的标签到知识库 if knowledge_tag_list: all_tag_dict, new_tag_dict = save_knowledge_tags(knowledge_id, knowledge_tag_list) # 构建文档-标签ID映射 document_tag_id_map = {} # 为每个文档添加其对应的标签 for doc_id, doc_tags in document_tags_map.items(): doc_tag_ids = [ all_tag_dict[(tag.get("key"), tag.get("value"))] for tag in doc_tags if (tag.get("key"), tag.get("value")) in all_tag_dict ] if doc_tag_ids: document_tag_id_map[doc_id] = doc_tag_ids if document_tag_id_map: batch_add_document_tag(document_tag_id_map) problem_model_list, problem_paragraph_mapping_list = ( ProblemParagraphManage(problem_paragraph_object_list, knowledge_id).to_problem_model_list() ) QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None if len(paragraph_model_list) > 0: for document in document_model_list: max_position = Paragraph.objects.filter(document_id=document.id).aggregate( max_position=Max('position') )['max_position'] or 0 sub_list = [p for p in paragraph_model_list if p.document_id == document.id] for i, paragraph in enumerate(sub_list): paragraph.position = max_position + i + 1 QuerySet(Paragraph).bulk_create(sub_list if len(sub_list) > 0 else []) bulk_create_in_batches(Problem, problem_model_list, batch_size=1000) bulk_create_in_batches(ProblemParagraphMapping, problem_paragraph_mapping_list, batch_size=1000) return document_model_list, knowledge_id, workspace_id @staticmethod def post_embedding(document_model_list, knowledge_id, workspace_id): for document in document_model_list: DocumentSerializers.Operate(data={ 'knowledge_id': knowledge_id, 'document_id': document.id, 'workspace_id': workspace_id }).refresh() def execute(self, documents, **kwargs) -> NodeResult: document_model_list, knowledge_id, workspace_id = self.save(documents) self.post_embedding(document_model_list, knowledge_id, workspace_id) write_content_list = [{ "name": document.get("name"), "paragraphs": [{ "title": p.get("title"), "content": p.get("content"), } for p in document.get("paragraphs")[0:5]] } for document in documents] return NodeResult({'write_content': write_content_list}, {}) 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, 'write_content': self.context.get("write_content"), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/loop_break_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/9/15 12:08 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/loop_break_node/i_loop_break_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: i_loop_break_node.py @date:2025/9/15 12:14 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode from application.flow.i_step_node import NodeResult class ConditionSerializer(serializers.Serializer): compare = serializers.CharField(required=True, label=_("Comparator")) value = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("value")) field = serializers.ListField(required=True, label=_("Fields")) class LoopBreakNodeSerializer(serializers.Serializer): condition = serializers.CharField(required=True, label=_("Condition or|and")) condition_list = ConditionSerializer(many=True) class ILoopBreakNode(INode): type = 'loop-break-node' support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return LoopBreakNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data) def execute(self, condition, condition_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/loop_break_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/9/15 12:16 @desc: """ from .base_loop_break_node import BaseLoopBreakNode ================================================ FILE: apps/application/flow/step_node/loop_break_node/impl/base_loop_break_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_loop_break_node.py @date:2025/9/15 12:17 @desc: """ import time from typing import List, Dict from application.flow.compare import compare_handle_list from application.flow.i_step_node import NodeResult from application.flow.step_node.loop_break_node.i_loop_break_node import ILoopBreakNode def _write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable.get("is_break"): yield "BREAK" node.context['run_time'] = time.time() - node.context['start_time'] class BaseLoopBreakNode(ILoopBreakNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') def execute(self, condition, condition_list, **kwargs) -> NodeResult: r = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in condition_list] is_break = all(r) if condition == 'and' else any(r) if is_break: self.node_params['is_result'] = True self.context['is_break'] = is_break return NodeResult({'is_break': is_break}, {}, _write_context=_write_context, _is_interrupt=lambda n, v, w: is_break) def assertion(self, field_list: List[str], compare: str, value): try: value = self.workflow_manage.generate_prompt(value) except Exception as e: pass field_value = None try: field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:]) except Exception as e: pass for compare_handler in compare_handle_list: if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value): return compare_handler.compare(field_value, compare, value) def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'is_break': self.context.get('is_break'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/loop_continue_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/9/15 12:08 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: i_loop_continue_node.py @date:2025/9/15 12:13 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class ConditionSerializer(serializers.Serializer): compare = serializers.CharField(required=True, label=_("Comparator")) value = serializers.CharField(required=True, label=_("value")) field = serializers.ListField(required=True, label=_("Fields")) class LoopContinueNodeSerializer(serializers.Serializer): condition = serializers.CharField(required=True, label=_("Condition or|and")) condition_list = ConditionSerializer(many=True) class ILoopContinueNode(INode): type = 'loop-continue-node' support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return LoopContinueNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data) def execute(self, condition, condition_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/loop_continue_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/9/15 12:13 @desc: """ from .base_loop_continue_node import BaseLoopContinueNode ================================================ FILE: apps/application/flow/step_node/loop_continue_node/impl/base_loop_continue_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_loop_continue_node.py @date:2025/9/15 12:13 @desc: """ from typing import List from application.flow.compare import compare_handle_list from application.flow.i_step_node import NodeResult from application.flow.step_node.loop_continue_node.i_loop_continue_node import ILoopContinueNode class BaseLoopContinueNode(ILoopContinueNode): def save_context(self, details, workflow_manage): self.context['exception_message'] = details.get('err_message') def execute(self, condition, condition_list, **kwargs) -> NodeResult: condition_list = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in condition_list] is_continue = all(condition_list) if condition == 'and' else any(condition_list) self.context['is_continue'] = is_continue if is_continue: return NodeResult({'is_continue': is_continue, 'branch_id': 'continue'}, {}) return NodeResult({'is_continue': is_continue}, {}) def assertion(self, field_list: List[str], compare: str, value): try: value = self.workflow_manage.generate_prompt(value) except Exception as e: pass field_value = None try: field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:]) except Exception as e: pass for compare_handler in compare_handle_list: if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value): return compare_handler.compare(field_value, compare, value) def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, "is_continue": self.context.get('is_continue'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/loop_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2025/3/11 18:24 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/loop_node/i_loop_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_loop_node.py @date:2025/3/11 18:19 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.exception.app_exception import AppApiException class ILoopNodeSerializer(serializers.Serializer): loop_type = serializers.CharField(required=True, label=_("loop_type")) array = serializers.ListField(required=False, allow_null=True, label=_("array")) number = serializers.IntegerField(required=False, allow_null=True, label=_("number")) loop_body = serializers.DictField(required=True, label="循环体") def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) loop_type = self.data.get('loop_type') if loop_type == 'ARRAY': array = self.data.get('array') if array is None or len(array) == 0: message = _('{field}, this field is required.', field='array') raise AppApiException(500, message) elif loop_type == 'NUMBER': number = self.data.get('number') if number is None: message = _('{field}, this field is required.', field='number') raise AppApiException(500, message) class ILoopNode(INode): type = 'loop-node' support = [WorkflowMode.APPLICATION, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return ILoopNodeSerializer def _run(self): array = self.node_params_serializer.data.get('array') if self.node_params_serializer.data.get('loop_type') == 'ARRAY': array = self.workflow_manage.get_reference_field( array[0], array[1:]) return self.execute(**{**self.node_params_serializer.data, "array": array}, **self.flow_params_serializer.data) def execute(self, loop_type, array, number, loop_body, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/loop_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2025/3/11 18:24 @desc: """ from .base_loop_node import BaseLoopNode ================================================ FILE: apps/application/flow/step_node/loop_node/impl/base_loop_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_loop_node.py @date:2025/3/11 18:24 @desc: """ import time from typing import Dict, List from django.utils.translation import gettext as _ from application.flow.common import Answer, WorkflowMode from application.flow.i_step_node import NodeResult, WorkFlowPostHandler, INode from application.flow.step_node.loop_node.i_loop_node import ILoopNode from application.flow.tools import Reasoning from application.models import ChatRecord from common.handle.impl.response.loop_to_response import LoopToResponse from maxkb.const import CONFIG max_loop_count = int(CONFIG.get("WORKFLOW_LOOP_NODE_MAX_LOOP_COUNT", 500)) def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict): return node.context.get('is_interrupt_exec', False) def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, reasoning_content: str): node.context['answer'] = answer node.context['run_time'] = time.time() - node.context['start_time'] node.context['reasoning_content'] = reasoning_content if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') workflow_manage = node_variable.get('workflow_manage') answer = '' reasoning_content = '' for chunk in response: content_chunk = chunk.get('content', '') reasoning_content_chunk = chunk.get('reasoning_content', '') reasoning_content += reasoning_content_chunk answer += content_chunk yield {'content': content_chunk, 'reasoning_content': reasoning_content_chunk} runtime_details = workflow_manage.get_runtime_details() _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result') model_setting = node.context.get('model_setting', {'reasoning_content_enable': False, 'reasoning_content_end': '
', 'reasoning_content_start': ''}) reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end')) reasoning_result = reasoning.get_reasoning_content(response) reasoning_result_end = reasoning.get_end_reasoning_content() content = reasoning_result.get('content') + reasoning_result_end.get('content') if 'reasoning_content' in response.response_metadata: reasoning_content = response.response_metadata.get('reasoning_content', '') else: reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content') _write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content) def get_answer_list(instance, child_node_node_dict, runtime_node_id): answer_list = instance.get_record_answer_list() for a in answer_list: _v = child_node_node_dict.get(a.get('runtime_node_id')) if _v: a['runtime_node_id'] = runtime_node_id a['child_node'] = _v return answer_list def insert_or_replace(arr, index, value): if index < len(arr): arr[index] = value # 替换 else: # 在末尾插入足够多的None,然后替换最后一个 arr.extend([None] * (index - len(arr) + 1)) arr[index] = value return arr def generate_loop_number(number: int): def i(current_index: int): return iter([(index, index) for index in range(current_index, number)]) return i def generate_loop_array(array): def i(current_index: int): return iter([(array[index], index) for index in range(current_index, len(array))]) return i def generate_while_loop(current_index: int): index = current_index while True: yield index, index index += 1 def loop(workflow_manage_new_instance, node: INode, generate_loop): loop_global_data = {} break_outer = False is_interrupt_exec = False loop_node_data = node.context.get('loop_node_data') or [] loop_answer_data = node.context.get("loop_answer_data") or [] start_index = node.context.get("current_index") or 0 current_index = start_index node_params = node.node_params start_node_id = node_params.get('child_node', {}).get('runtime_node_id') loop_type = node_params.get('loop_type') start_node_data = None chat_record = None child_node = None if start_node_id: chat_record_id = node_params.get('child_node', {}).get('chat_record_id') child_node = node_params.get('child_node', {}).get('child_node') start_node_data = node_params.get('node_data') chat_record = ChatRecord(id=chat_record_id, answer_text_list=[], answer_text='', details=loop_node_data[current_index]) for item, index in generate_loop(current_index): if 0 < max_loop_count <= index - start_index and loop_type == 'LOOP': raise Exception(_('Exceeding the maximum number of cycles')) """ 指定次数循环 @return: """ instance = workflow_manage_new_instance({'index': index, 'item': item}, loop_global_data, start_node_id, start_node_data, chat_record, child_node) response = instance.stream() answer = '' current_index = index reasoning_content = '' child_node_node_dict = {} for chunk in response: if chunk.get('node_type') == 'loop-break-node' and chunk.get('content', '') == 'BREAK': break_outer = True continue child_node = chunk.get('child_node') runtime_node_id = chunk.get('runtime_node_id', '') chat_record_id = chunk.get('chat_record_id', '') child_node_node_dict[runtime_node_id] = { 'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'child_node': child_node} content_chunk = (chunk.get('content', '') or '') reasoning_content_chunk = (chunk.get('reasoning_content', '') or '') if chunk.get('real_node_id'): chunk['real_node_id'] = chunk['real_node_id'] + '__' + str(index) reasoning_content += reasoning_content_chunk answer += content_chunk yield chunk if chunk.get('node_status', "SUCCESS") == 'ERROR': insert_or_replace(loop_node_data, index, instance.get_runtime_details()) insert_or_replace(loop_answer_data, index, get_answer_list(instance, child_node_node_dict, node.runtime_node_id)) node.context['is_interrupt_exec'] = is_interrupt_exec node.context['loop_node_data'] = loop_node_data node.context['loop_answer_data'] = loop_answer_data node.context["index"] = current_index node.context["item"] = current_index node.status = 500 node.err_message = chunk.get('content') return node_type = chunk.get('node_type') if node_type == 'form-node': break_outer = True is_interrupt_exec = True start_node_id = None start_node_data = None chat_record = None child_node = None insert_or_replace(loop_node_data, index, instance.get_runtime_details()) insert_or_replace(loop_answer_data, index, get_answer_list(instance, child_node_node_dict, node.runtime_node_id)) instance._cleanup() if break_outer: break if instance.is_the_task_interrupted(): break node.context['is_interrupt_exec'] = is_interrupt_exec node.context['loop_node_data'] = loop_node_data node.context['loop_answer_data'] = loop_answer_data node.context["index"] = current_index node.context["item"] = current_index node.context['run_time'] = time.time() - node.context.get("start_time") def get_tokens(loop_node_data): message_tokens = 0 answer_tokens = 0 for details in loop_node_data: message_tokens += sum([row.get('message_tokens') for row in details.values() if 'message_tokens' in row and row.get('message_tokens') is not None]) answer_tokens += sum([row.get('answer_tokens') for row in details.values() if 'answer_tokens' in row and row.get('answer_tokens') is not None]) return {'message_tokens': message_tokens, 'answer_tokens': answer_tokens} def get_write_context(loop_type, array, number, loop_body): def inner_write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): if loop_type == 'ARRAY': return loop(node_variable['workflow_manage_new_instance'], node, generate_loop_array(array)) if loop_type == 'LOOP': return loop(node_variable['workflow_manage_new_instance'], node, generate_while_loop) return loop(node_variable['workflow_manage_new_instance'], node, generate_loop_number(number)) return inner_write_context class LoopWorkFlowPostHandler(WorkFlowPostHandler): def handler(self, workflow): pass class BaseLoopNode(ILoopNode): def save_context(self, details, workflow_manage): self.context['loop_context_data'] = details.get('loop_context_data') self.context['loop_answer_data'] = details.get('loop_answer_data') self.context['loop_node_data'] = details.get('loop_node_data') self.context['result'] = details.get('result') self.context['params'] = details.get('params') self.context['run_time'] = details.get('run_time') self.context['index'] = details.get('current_index') self.context['item'] = details.get('current_item') for key, value in (details.get('loop_context_data') or {}).items(): self.context[key] = value self.answer_text = "" def get_answer_list(self) -> List[Answer] | None: result = [] for answer_list in (self.context.get("loop_answer_data") or []): for a in answer_list: if isinstance(a, dict): result.append(Answer(**a)) return result def get_loop_context(self): return self.context def execute(self, loop_type, array, number, loop_body, **kwargs) -> NodeResult: from application.flow.loop_workflow_manage import LoopWorkflowManage, Workflow from application.flow.knowledge_loop_workflow_manage import KnowledgeLoopWorkflowManage from application.flow.tool_loop_workflow_manage import ToolLoopWorkflowManage def workflow_manage_new_instance(loop_data, global_data, start_node_id=None, start_node_data=None, chat_record=None, child_node=None): workflow_mode = {WorkflowMode.APPLICATION: WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE: WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL: WorkflowMode.TOOL_LOOP}.get( self.workflow_manage.flow.workflow_mode) or WorkflowMode.APPLICATION c = {WorkflowMode.APPLICATION_LOOP: LoopWorkflowManage, WorkflowMode.KNOWLEDGE_LOOP: KnowledgeLoopWorkflowManage, WorkflowMode.TOOL_LOOP: ToolLoopWorkflowManage}.get(workflow_mode) or LoopWorkflowManage workflow_manage = c(Workflow.new_instance(loop_body, workflow_mode), self.workflow_manage.params, LoopWorkFlowPostHandler( self.workflow_manage.work_flow_post_handler.chat_info), self.workflow_manage, loop_data, self.get_loop_context, base_to_response=LoopToResponse(), start_node_id=start_node_id, start_node_data=start_node_data, chat_record=chat_record, child_node=child_node, is_the_task_interrupted=self.workflow_manage.is_the_task_interrupted ) return workflow_manage return NodeResult({'workflow_manage_new_instance': workflow_manage_new_instance}, {}, _write_context=get_write_context(loop_type, array, number, loop_body), _is_interrupt=_is_interrupt_exec) def get_loop_context_data(self): fields = self.node.properties.get('config', []).get('fields', []) or [] return {f.get('value'): self.context.get(f.get('value')) for f in fields if self.context.get(f.get('value')) is not None} def get_details(self, index: int, **kwargs): tokens = get_tokens(self.context.get("loop_node_data")) return { 'name': self.node.properties.get('stepName'), "index": index, "result": self.context.get('result'), 'array': self.node_params_serializer.data.get('array'), 'number': self.node_params_serializer.data.get('number'), "params": self.context.get('params'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'current_index': self.context.get("index"), "current_item": self.context.get("item"), 'loop_type': self.node_params_serializer.data.get('loop_type'), 'status': self.status, 'loop_context_data': self.get_loop_context_data(), 'loop_node_data': self.context.get("loop_node_data"), 'loop_answer_data': self.context.get("loop_answer_data"), 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), 'message_tokens': tokens.get('message_tokens') or 0, 'answer_tokens': tokens.get('answer_tokens') or 0, } ================================================ FILE: apps/application/flow/step_node/loop_start_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:30 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/loop_start_node/i_loop_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_start_node.py @date:2024/6/3 16:54 @desc: """ from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class ILoopStarNode(INode): type = 'loop-start-node' support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP] def _run(self): return self.execute(**self.flow_params_serializer.data) def execute(self, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/loop_start_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:36 @desc: """ from .base_start_node import BaseLoopStartStepNode ================================================ FILE: apps/application/flow/step_node/loop_start_node/impl/base_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_start_node.py @date:2024/6/3 17:17 @desc: """ from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import NodeResult from application.flow.step_node.loop_start_node.i_loop_start_node import ILoopStarNode class BaseLoopStartStepNode(ILoopStarNode): def save_context(self, details, workflow_manage): self.context['index'] = details.get('current_index') self.context['item'] = details.get('current_item') self.context['exception_message'] = details.get('err_message') def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: pass def execute(self, **kwargs) -> NodeResult: """ 开始节点 初始化全局变量 """ loop_params = self.workflow_manage.loop_params node_variable = { 'index': loop_params.get("index"), 'item': loop_params.get("item") } if WorkflowMode.APPLICATION_LOOP == self.workflow_manage.flow.workflow_mode: self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable() return NodeResult(node_variable, {}) def get_details(self, index: int, **kwargs): global_fields = [] for field in self.node.properties.get('config')['globalFields']: key = field['value'] global_fields.append({ 'label': field['label'], 'key': key, 'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else '' }) return { 'name': self.node.properties.get('stepName'), "index": index, "current_index": self.context.get('index'), "current_item": self.context.get('item'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/mcp_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/mcp_node/i_mcp_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class McpNodeSerializer(serializers.Serializer): mcp_servers = serializers.JSONField(required=True, label=_("Mcp servers")) mcp_server = serializers.CharField(required=True, label=_("Mcp server")) mcp_tool = serializers.CharField(required=True, label=_("Mcp tool")) mcp_tool_id = serializers.CharField(required=False, label=_("Mcp tool"), allow_null=True, allow_blank=True) mcp_source = serializers.CharField(required=False, label=_("Mcp source"), allow_blank=True, allow_null=True) tool_params = serializers.DictField(required=True, label=_("Tool parameters")) class IMcpNode(INode): type = 'mcp-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return McpNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, mcp_servers, mcp_server, mcp_tool, mcp_tool_id, mcp_source, tool_params, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/mcp_node/impl/__init__.py ================================================ # coding=utf-8 from .base_mcp_node import BaseMcpNode ================================================ FILE: apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py ================================================ # coding=utf-8 import asyncio import json from typing import List from django.db.models import QuerySet from langchain_mcp_adapters.client import MultiServerMCPClient from application.flow.i_step_node import NodeResult from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode from tools.models import Tool class BaseMcpNode(IMcpNode): def save_context(self, details, workflow_manage): self.context['result'] = details.get('result') self.context['tool_params'] = details.get('tool_params') self.context['mcp_tool'] = details.get('mcp_tool') self.context['exception_message'] = details.get('err_message') def execute(self, mcp_servers, mcp_server, mcp_tool, mcp_tool_id, mcp_source, tool_params, **kwargs) -> NodeResult: if mcp_source == 'referencing': if not mcp_tool_id: raise ValueError("MCP tool ID is required when mcp_source is 'referencing'.") tool = QuerySet(Tool).filter(id=mcp_tool_id).first() if not tool: raise ValueError(f"Tool with ID {mcp_tool_id} not found.") if not tool.is_active: raise ValueError(f"Tool with ID {mcp_tool_id} is inactive.") servers = json.loads(tool.code) servers = self.handle_variables(servers) # 处理servers中的变量 params = json.loads(json.dumps(tool_params)) params = self.handle_variables(params) else: servers = json.loads(mcp_servers) servers = self.handle_variables(servers) # 处理servers中的变量 params = json.loads(json.dumps(tool_params)) params = self.handle_variables(params) async def call_tool(t, a): client = MultiServerMCPClient(servers) async with client.session(mcp_server) as s: return await s.call_tool(t, a) res = asyncio.run(call_tool(mcp_tool, params)) return NodeResult( {'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {}) def handle_variables(self, tool_params): # 处理参数中的变量 for k, v in tool_params.items(): if type(v) == str: tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k]) if type(v) == dict: self.handle_variables(v) if (type(v) == list) and (type(v[0]) == str): tool_params[k] = self.get_reference_content(v) return tool_params def get_reference_content(self, fields: List[str]): return 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'), 'status': self.status, 'err_message': self.err_message, 'type': self.node.type, 'mcp_tool': self.context.get('mcp_tool'), 'tool_params': self.context.get('tool_params'), 'result': self.context.get('result'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/parameter_extraction_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/10/13 14:56 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class VariableSplittingNodeParamsSerializer(serializers.Serializer): input_variable = serializers.ListField(required=True, label=_("input variable")) variable_list = serializers.ListField(required=True, label=_("Split variables")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) model_id = serializers.CharField(required=True, label=_("Model id")) class IParameterExtractionNode(INode): type = 'parameter-extraction-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableSplittingNodeParamsSerializer def _run(self): input_variable = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('input_variable')[0], self.node_params_serializer.data.get('input_variable')[1:]) return self.execute(input_variable, self.node_params_serializer.data['variable_list'], self.node_params_serializer.data['model_params_setting'], self.node_params_serializer.data['model_id']) def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/parameter_extraction_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/10/13 15:01 @desc: """ from .base_parameter_extraction_node import * ================================================ FILE: apps/application/flow/step_node/parameter_extraction_node/impl/base_parameter_extraction_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_variable_splitting_node.py @date:2025/10/13 15:02 @desc: """ import json import re from django.db.models import QuerySet from langchain_core.messages import HumanMessage from langchain_core.prompts import PromptTemplate from application.flow.i_step_node import NodeResult from application.flow.step_node.parameter_extraction_node.i_parameter_extraction_node import IParameterExtractionNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential prompt = """ Please strictly process the text according to the following requirements: **Task**: Extract specified field information from given text **Enter text**: {{question}} **Extract configuration**: {{properties}} **Rule**: - Strictly follow the data and field of Extract configuration - If not found, use null value - Only return pure JSON without additional text - Keep the string format neat """ def get_default_model_params_setting(model_id): model = QuerySet(Model).filter(id=model_id).first() credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = credential.get_model_params_setting_form( model.model_name).get_default_form_data() return model_params_setting def generate_properties(variable_list): return {variable['field']: {'type': variable['parameter_type'], 'description': (variable.get('desc') or ""), 'title': variable['label']} for variable in variable_list} def generate_example(variable_list): return {variable['field']: None for variable in variable_list} def generate_content(input_variable, variable_list): properties = generate_properties(variable_list) prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2') value = prompt_template.format(properties=properties, question=input_variable) return value def json_loads(response, expected_fields): if not response or not isinstance(response, str): return {field: None for field in expected_fields} cleaned = response.strip() extraction_strategies = [ lambda: json.loads(cleaned), lambda: json.loads(re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned, re.DOTALL).group(1)), lambda: json.loads(re.search(r'(\{[\s\S]*\})', cleaned).group(1)), ] for strategy in extraction_strategies: try: result = strategy() return result except: continue return generate_example(expected_fields) class BaseParameterExtractionNode(IParameterExtractionNode): def save_context(self, details, workflow_manage): for key, value in details.get('result').items(): self.context[key] = value self.context['result'] = details.get('result') self.context['request'] = details.get('request') self.context['exception_message'] = details.get('err_message') def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult: input_variable = str(input_variable) self.context['request'] = input_variable if model_params_setting is None: model_params_setting = get_default_model_params_setting(model_id) workspace_id = self.workflow_manage.get_body().get('workspace_id') chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) content = generate_content(input_variable, variable_list) response = chat_model.invoke([HumanMessage(content=content)]) result = json_loads(response.content, variable_list) return NodeResult({'result': result, **result}, {}) 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, 'request': self.context.get('request'), 'result': self.context.get('result'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/question_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:30 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/question_node/i_question_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_chat_node.py @date:2024/6/4 13:58 @desc: """ from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class QuestionNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Role Setting")) prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=True, label= _("Number of multi-round conversations")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) class IQuestionNode(INode): type = 'question-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return QuestionNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/question_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:35 @desc: """ from .base_question_node import BaseQuestionNode ================================================ FILE: apps/application/flow/step_node/question_node/impl/base_question_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_question_node.py @date:2024/6/4 14:30 @desc: """ import re import time from functools import reduce from typing import List, Dict from django.db.models import QuerySet from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage from application.flow.i_step_node import NodeResult, INode from application.flow.step_node.question_node.i_question_node import IQuestionNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): chat_model = node_variable.get('chat_model') message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list')) answer_tokens = chat_model.get_num_tokens(answer) node.context['message_tokens'] = message_tokens node.context['answer_tokens'] = answer_tokens node.context['answer'] = answer node.context['history_message'] = node_variable['history_message'] node.context['question'] = node_variable['question'] node.context['run_time'] = time.time() - node.context['start_time'] if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = '' for chunk in response: answer += chunk.content yield chunk.content _write_context(node_variable, workflow_variable, node, workflow, answer) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = response.content _write_context(node_variable, workflow_variable, node, workflow, answer) def get_default_model_params_setting(model_id): model = QuerySet(Model).filter(id=model_id).first() credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = credential.get_model_params_setting_form( model.model_name).get_default_form_data() return model_params_setting class BaseQuestionNode(IQuestionNode): def save_context(self, details, workflow_manage): self.context['run_time'] = details.get('run_time') self.context['question'] = details.get('question') self.context['answer'] = details.get('answer') self.context['message_tokens'] = details.get('message_tokens') self.context['answer_tokens'] = details.get('answer_tokens') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, model_params_setting=None, **kwargs) -> NodeResult: if model_params_setting is None: model_params_setting = get_default_model_params_setting(model_id) workspace_id = self.workflow_manage.get_body().get('workspace_id') chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question.content system = self.workflow_manage.generate_prompt(system) self.context['system'] = system message_list = self.generate_message_list(system, prompt, history_message) self.context['message_list'] = message_list if stream: r = chat_model.stream(message_list) return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context_stream) else: r = chat_model.invoke(message_list) return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context) @staticmethod def get_history_message(history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) for message in history_message: if isinstance(message.content, str): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message def generate_prompt_question(self, prompt): return HumanMessage(self.workflow_manage.generate_prompt(prompt)) def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'system': self.context.get('system'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/reranker_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/9/4 11:37 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/reranker_node/i_reranker_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_reranker_node.py @date:2024/9/4 10:40 @desc: """ from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class RerankerSettingSerializer(serializers.Serializer): # 需要查询的条数 top_n = serializers.IntegerField(required=True, label=_("Reference segment number")) # 相似度 0-1之间 similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_("Reference segment number")) max_paragraph_char_number = serializers.IntegerField(required=True, label=_("Maximum number of words in a quoted segment")) class RerankerStepNodeSerializer(serializers.Serializer): reranker_setting = RerankerSettingSerializer(required=True) question_reference_address = serializers.ListField(required=True) reranker_model_id = serializers.UUIDField(required=True) reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True)) show_knowledge = serializers.BooleanField(required=True, label=_("The results are displayed in the knowledge sources")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) class IRerankerNode(INode): type = 'reranker-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return RerankerStepNodeSerializer def _run(self): question = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('question_reference_address')[0], self.node_params_serializer.data.get('question_reference_address')[1:]) reranker_list = [self.workflow_manage.get_reference_field( reference[0], reference[1:]) for reference in self.node_params_serializer.data.get('reranker_reference_list')] return self.execute(**self.node_params_serializer.data, question=str(question), reranker_list=reranker_list) def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/reranker_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/9/4 11:39 @desc: """ from .base_reranker_node import * ================================================ FILE: apps/application/flow/step_node/reranker_node/impl/base_reranker_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_reranker_node.py @date:2024/9/4 11:41 @desc: """ from typing import List from langchain_core.documents import Document from application.flow.i_step_node import NodeResult from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode from models_provider.tools import get_model_instance_by_model_workspace_id def merge_reranker_list(reranker_list, result=None): if result is None: result = [] for document in reranker_list: if isinstance(document, list): merge_reranker_list(document, result) elif isinstance(document, dict): content = document.get('title', '') + document.get('content', '') title = document.get("title") result.append( Document(page_content=str(document) if len(content) == 0 else content, metadata={'title': title, **document})) else: result.append(Document(page_content=str(document), metadata={})) return result def filter_result(document_list: List[Document], max_paragraph_char_number, top_n, similarity): use_len = 0 result = [] for index in range(len(document_list)): document = document_list[index] if use_len >= max_paragraph_char_number or index >= top_n or document.metadata.get( 'relevance_score') < similarity: break content = document.page_content[0:max_paragraph_char_number - use_len] use_len = use_len + len(content) result.append({'page_content': content, 'metadata': document.metadata}) return result def reset_result_list(result_list: List[Document], document_list: List[Document]): r = [] document_list = document_list.copy() for result in result_list: filter_result_list = [document for document in document_list if document.page_content == result.page_content] if len(filter_result_list) > 0: item = filter_result_list[0] document_list.remove(item) r.append(Document(page_content=item.page_content, metadata={**item.metadata, 'relevance_score': result.metadata.get('relevance_score')})) else: r.append(result) return r def get_none_result(question): return NodeResult( {'document_list': [], 'question': question, 'result_list': [], 'result': ''}, {}) def reset_metadata(metadata): meta = metadata.get('meta') if isinstance(metadata.get('meta'), dict): if not meta.get('allow_download', False): metadata['meta'] = {'allow_download': False} return metadata class BaseRerankerNode(IRerankerNode): def save_context(self, details, workflow_manage): self.context['document_list'] = details.get('document_list', []) self.context['question'] = details.get('question') self.context['run_time'] = details.get('run_time') self.context['result_list'] = details.get('result_list') self.context['result'] = details.get('result') self.context['exception_message'] = details.get('err_message') def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge, **kwargs) -> NodeResult: self.context['show_knowledge'] = show_knowledge documents = merge_reranker_list(reranker_list) documents = [d for d in documents if d.page_content and len(d.page_content) > 0] if len(documents) == 0: return get_none_result(question) top_n = reranker_setting.get('top_n', 3) self.context['document_list'] = [ {'page_content': document.page_content, 'metadata': reset_metadata(document.metadata)} for document in documents] self.context['question'] = question workspace_id = self.workflow_manage.get_body().get('workspace_id') reranker_model = get_model_instance_by_model_workspace_id(reranker_model_id, workspace_id, top_n=top_n) result = reranker_model.compress_documents( documents, question) similarity = reranker_setting.get('similarity', 0.6) max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000) result = reset_result_list(result, documents) r = filter_result(result, max_paragraph_char_number, top_n, similarity) return NodeResult({'result_list': r, 'result': ''.join([item.get('page_content') for item in r])}, {}) def get_details(self, index: int, **kwargs): return { 'show_knowledge': self.context.get('show_knowledge'), 'name': self.node.properties.get('stepName'), "index": index, 'document_list': self.context.get('document_list'), "question": self.context.get('question'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'reranker_setting': self.node_params_serializer.data.get('reranker_setting'), 'result_list': self.context.get('result_list'), 'result': self.context.get('result'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/search_document_node/__init__.py ================================================ from .impl import * ================================================ FILE: apps/application/flow/step_node/search_document_node/i_search_document_node.py ================================================ # coding=utf-8 from typing import Type, List from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class SearchDocumentStepNodeSerializer(serializers.Serializer): knowledge_id_list = serializers.ListField( required=False, child=serializers.UUIDField(required=True), label=_("knowledge id list"), default=list ) search_mode = serializers.ChoiceField( required=False, choices=['auto', 'custom'], label=_("search mode"), default='auto' ) search_scope_type = serializers.ChoiceField( required=False, choices=['custom', 'referencing'], label=_("search scope type"), allow_null=True, default='custom' ) search_scope_source = serializers.ChoiceField( required=False, choices=['document', 'knowledge'], label=_("search scope variable type"), default='knowledge' ) search_scope_reference = serializers.ListField( required=False, label=_("search scope variable"), default=list ) question_reference = serializers.ListField( required=False, label=_("question reference address"), default=list ) search_condition_type = serializers.ChoiceField( required=False, choices=['AND', 'OR'], label=_("search condition type"), default='AND' ) search_condition_list = serializers.ListField( required=False, label=_("search condition list"), default=list ) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) class ISearchDocumentStepNode(INode): type = 'search-document-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SearchDocumentStepNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, knowledge_id_list: List, search_mode: str, search_scope_type: str, search_scope_source: str, search_scope_reference: List, question_reference: List, search_condition_type: str, search_condition_list: List, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/search_document_node/impl/__init__.py ================================================ from .base_search_document_node import BaseSearchDocumentNode ================================================ FILE: apps/application/flow/step_node/search_document_node/impl/base_search_document_node.py ================================================ # coding=utf-8 from typing import List import jieba from django.db.models import Q from django.db.models import QuerySet from application.flow.i_step_node import NodeResult from application.flow.step_node.search_document_node.i_search_document_node import ISearchDocumentStepNode from common.constants.permission_constants import RoleConstants from common.database_model_manage.database_model_manage import DatabaseModelManage from common.utils.shared_resource_auth import filter_authorized_ids from knowledge.models import Document, DocumentTag, Knowledge class BaseSearchDocumentNode(ISearchDocumentStepNode): def save_context(self, details, workflow_manage): self.context['document_list'] = details.get('document_list') self.context['knowledge_list'] = details.get('knowledge_list') self.context['document_items'] = details.get('document_items') self.context['knowledge_items'] = details.get('knowledge_items') self.context['question'] = details.get('question') self.context['run_time'] = details.get('run_time') self.context['exception_message'] = details.get('err_message') def get_reference_content(self, fields: List[str]): return self.workflow_manage.get_reference_field(fields[0], fields[1:]) def execute(self, knowledge_id_list: List, search_mode: str, search_scope_type: str, search_scope_source: str, search_scope_reference: List, question_reference: List, search_condition_type: str, search_condition_list: List, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') if search_scope_type == 'custom': # 手动选择知识库 knowledge_id_list = filter_authorized_ids('knowledge', knowledge_id_list, workspace_id) document_id_list = QuerySet(Document).filter( knowledge_id__in=knowledge_id_list ).values_list('id', flat=True) else: # 引用上一步知识库/文档 if search_scope_source == 'document': # 文档 document_id_list = self.get_reference_content(search_scope_reference) else: # 知识库 ref_knowledge_ids = filter_authorized_ids('knowledge', self.get_reference_content(search_scope_reference), workspace_id) document_id_list = QuerySet(Document).filter( knowledge_id__in=ref_knowledge_ids ).values_list('id', flat=True) # 权限过滤 get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized') chat_user_type = self.workflow_manage.get_body().get('chat_user_type') if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type: actual_knowledge_ids = list( QuerySet(Document).filter(id__in=document_id_list) .values_list('knowledge_id', flat=True).distinct() ) authorized_knowledge_ids = get_knowledge_list_of_authorized( self.workflow_manage.get_body().get('chat_user_id'), actual_knowledge_ids ) document_id_list = QuerySet(Document).filter( id__in=document_id_list, knowledge_id__in=authorized_knowledge_ids ).values_list('id', flat=True) if search_mode == 'auto': # 通过问题自动检索 matched_doc_ids = self.handle_auto_tags(document_id_list, question_reference) final_document_ids = list(matched_doc_ids) else: # 自定义检索条件 matched_document_ids = self.handle_custom_tags( document_id_list, search_condition_list, search_condition_type ) final_document_ids = list(matched_document_ids) # UUID to str final_document_ids = [str(doc_id) for doc_id in final_document_ids] document_items = QuerySet(Document).filter(id__in=final_document_ids).values() final_knowledge_ids = list(set(str(doc['knowledge_id']) for doc in document_items)) knowledge_items = QuerySet(Knowledge).filter(id__in=final_knowledge_ids).values() return NodeResult({ 'document_list': final_document_ids, 'document_items': list(document_items), 'knowledge_list': final_knowledge_ids, 'knowledge_items': list(knowledge_items) }, {}) def handle_auto_tags(self, document_id_list: list, question_reference: list): question = self.get_reference_content(question_reference) # 使用jieba分词 keywords = jieba.lcut(question) if not keywords: return set() # 构建OR查询,一次性获取所有匹配的文档 q_objects = Q() for keyword in keywords: q_objects |= Q(tag__value__icontains=keyword) # 单次数据库查询 matched_doc_ids = set( QuerySet(DocumentTag) .filter(document_id__in=document_id_list) .filter(q_objects) .values_list('document_id', flat=True) .distinct() ) return matched_doc_ids def handle_custom_tags(self, document_id_list: List, search_condition_list: list, search_condition_type: str): if not search_condition_list: return set(document_id_list) if search_condition_type == 'AND': # AND逻辑:使用子查询和聚合 matched_doc_ids = set(document_id_list) for condition in search_condition_list: tag_key = condition['key'] field_value = self.workflow_manage.generate_prompt(condition['value']) compare_type = condition['compare'] if not field_value or field_value == 'None' or len(field_value) == 0: continue # 构建查询条件 if compare_type == 'not_contain': # 反向查询:找出包含该标签的文档,然后排除 exclude_docs = set(QuerySet(DocumentTag).filter( document_id__in=matched_doc_ids, tag__key=tag_key, tag__value__icontains=field_value ).values_list('document_id', flat=True).distinct()) matched_doc_ids = matched_doc_ids - exclude_docs else: if compare_type == 'contain': q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value) elif compare_type == 'eq': q_filter = Q(tag__key=tag_key, tag__value=field_value) else: continue # 单次查询获取符合条件的文档 tag_docs = set(QuerySet(DocumentTag).filter( document_id__in=matched_doc_ids ).filter(q_filter).values_list('document_id', flat=True).distinct()) matched_doc_ids = matched_doc_ids.intersection(tag_docs) return matched_doc_ids else: # OR逻辑 matched_docs = set() for condition in search_condition_list: tag_key = condition['key'] field_value = self.workflow_manage.generate_prompt(condition['value']) compare_type = condition['compare'] if not field_value or field_value == 'None' or len(field_value) == 0: continue if compare_type == 'not_contain': # 反向查询:找出包含该标签的文档,然后用全集减去 exclude_docs = set(QuerySet(DocumentTag).filter( document_id__in=document_id_list, tag__key=tag_key, tag__value__icontains=field_value ).values_list('document_id', flat=True).distinct()) matched_docs = matched_docs.union(set(document_id_list) - exclude_docs) else: if compare_type == 'contain': q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value) elif compare_type == 'eq': q_filter = Q(tag__key=tag_key, tag__value=field_value) else: continue docs = set(QuerySet(DocumentTag).filter( document_id__in=document_id_list ).filter(q_filter).values_list('document_id', flat=True).distinct()) matched_docs = matched_docs.union(docs) return matched_docs def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), 'question': self.context.get('question'), "index": index, 'run_time': self.context.get('run_time'), 'document_list': self.context.get('document_list'), 'knowledge_list': self.context.get('knowledge_list'), 'document_items': self.context.get('document_items'), 'knowledge_items': self.context.get('knowledge_items'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/search_knowledge_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:30 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_search_dataset_node.py @date:2024/6/3 17:52 @desc: """ import re from typing import Type from django.core import validators from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.utils.common import flat_map class DatasetSettingSerializer(serializers.Serializer): # 需要查询的条数 top_n = serializers.IntegerField(required=True, label=_("Reference segment number")) # 相似度 0-1之间 similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_('similarity')) search_mode = serializers.CharField(required=True, validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_("The type only supports embedding|keywords|blend"), code=500) ], label=_("Retrieval Mode")) max_paragraph_char_number = serializers.IntegerField(required=True, label=_("Maximum number of words in a quoted segment")) class SearchDatasetStepNodeSerializer(serializers.Serializer): # 需要查询的数据集id列表 knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_("Dataset id list")) knowledge_setting = DatasetSettingSerializer(required=True) question_reference_address = serializers.ListField(required=True) show_knowledge = serializers.BooleanField(required=True, label=_("The results are displayed in the knowledge sources")) search_scope_type = serializers.ChoiceField( required=False, choices=['custom', 'referencing'], label=_("search scope type"), allow_null=True, default='custom' ) search_scope_source = serializers.ChoiceField( required=False, choices=['document', 'knowledge'], label=_("search scope variable type"), default='knowledge' ) search_scope_reference = serializers.ListField( required=False, label=_("search scope variable"), default=list ) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) def get_paragraph_list(chat_record, node_id): return flat_map([chat_record.details[key].get('paragraph_list', []) for key in chat_record.details if (chat_record.details[ key].get('type', '') == 'search-dataset-node') and chat_record.details[key].get( 'paragraph_list', []) is not None and key == node_id]) class ISearchKnowledgeStepNode(INode): type = 'search-knowledge-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SearchDatasetStepNodeSerializer def _run(self): question = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('question_reference_address')[0], self.node_params_serializer.data.get('question_reference_address')[1:]) exclude_paragraph_id_list = [] if self.flow_params_serializer.data.get('re_chat', False): history_chat_record = self.flow_params_serializer.data.get('history_chat_record', []) paragraph_id_list = [p.get('id') for p in flat_map( [get_paragraph_list(chat_record, self.runtime_node_id) for chat_record in history_chat_record if chat_record.problem_text == question])] exclude_paragraph_id_list = list(set(paragraph_id_list)) return self.execute(**self.node_params_serializer.data, question=str(question), exclude_paragraph_id_list=exclude_paragraph_id_list) def execute(self, dataset_id_list, dataset_setting, question, show_knowledge, search_scope_type, search_scope_source, search_scope_reference, exclude_paragraph_id_list=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/search_knowledge_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:35 @desc: """ from .base_search_knowledge_node import BaseSearchKnowledgeNode ================================================ FILE: apps/application/flow/step_node/search_knowledge_node/impl/base_search_knowledge_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_search_dataset_node.py @date:2024/6/4 11:56 @desc: """ import os from typing import List, Dict from django.db import connection from django.db.models import QuerySet from application.flow.i_step_node import NodeResult from application.flow.step_node.search_knowledge_node.i_search_knowledge_node import ISearchKnowledgeStepNode from common.config.embedding_config import VectorStore from common.constants.permission_constants import RoleConstants from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search from common.utils.common import get_file_content from common.utils.shared_resource_auth import filter_authorized_ids from knowledge.models import Document, Paragraph, Knowledge, SearchMode from maxkb.conf import PROJECT_DIR from models_provider.tools import get_model_instance_by_model_workspace_id def get_embedding_id(dataset_id_list): dataset_list = QuerySet(Knowledge).filter(id__in=dataset_id_list) if len(set([dataset.embedding_model_id for dataset in dataset_list])) > 1: raise Exception("关联知识库的向量模型不一致,无法召回分段。") if len(dataset_list) == 0: raise Exception("知识库设置错误,请重新设置知识库") return dataset_list[0].embedding_model_id def get_none_result(question): return NodeResult( {'paragraph_list': [], 'is_hit_handling_method': [], 'question': question, 'data': '', 'directly_return': ''}, {}) def reset_title(title): if title is None or len(title.strip()) == 0: return "" else: return f"#### {title}\n" def reset_meta(meta): if not meta.get('allow_download', False): return {'allow_download': False} return meta class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode): def save_context(self, details, workflow_manage): result = details.get('paragraph_list', []) knowledge_setting = self.node_params_serializer.data.get('knowledge_setting') directly_return = '\n'.join( [f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result if paragraph.get('is_hit_handling_method')]) self.context['paragraph_list'] = result self.context['question'] = details.get('question') self.context['run_time'] = details.get('run_time') self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')] self.context['data'] = '\n'.join( [f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)] self.context['directly_return'] = directly_return self.context['exception_message'] = details.get('err_message') def get_reference_content(self, fields: List[str]): return self.workflow_manage.get_reference_field(fields[0], fields[1:]) def execute(self, knowledge_id_list, knowledge_setting, question, show_knowledge, search_scope_type, search_scope_source, search_scope_reference, exclude_paragraph_id_list=None, **kwargs) -> NodeResult: self.context['question'] = question self.context['show_knowledge'] = show_knowledge document_id_list = None if search_scope_type == 'referencing': # 引用上一步知识库/文档 if search_scope_source == 'knowledge': # 知识库 knowledge_id_list = self.get_reference_content(search_scope_reference) else: # 文档 document_id_list = self.get_reference_content(search_scope_reference) knowledge_id_list = [str(k) for k in QuerySet(Document).filter( id__in=document_id_list ).values_list( 'knowledge_id', flat=True ).distinct()] get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized') chat_user_type = self.workflow_manage.get_body().get('chat_user_type') if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type: knowledge_id_list = get_knowledge_list_of_authorized(self.workflow_manage.get_body().get('chat_user_id'), knowledge_id_list) workspace_id = self.workflow_manage.get_body().get('workspace_id') knowledge_id_list = filter_authorized_ids('knowledge', knowledge_id_list, workspace_id) if len(knowledge_id_list) == 0: return get_none_result(question) model_id = get_embedding_id(knowledge_id_list) embedding_model = get_model_instance_by_model_workspace_id(model_id, workspace_id) embedding_value = embedding_model.embed_query(question) vector = VectorStore.get_embedding_vector() exclude_document_id_list = [str(document.id) for document in QuerySet(Document).filter( knowledge_id__in=knowledge_id_list, is_active=False)] embedding_list = vector.query(question, embedding_value, knowledge_id_list, document_id_list, exclude_document_id_list, exclude_paragraph_id_list, True, knowledge_setting.get('top_n'), knowledge_setting.get('similarity'), SearchMode(knowledge_setting.get('search_mode'))) # 手动关闭数据库连接 connection.close() if embedding_list is None: return get_none_result(question) paragraph_list = self.list_paragraph(embedding_list, vector) result = [self.reset_paragraph(paragraph, embedding_list) for paragraph in paragraph_list] result = sorted(result, key=lambda p: p.get('similarity'), reverse=True) return NodeResult({'paragraph_list': result, 'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')], 'data': '\n'.join( [f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)], 'directly_return': '\n'.join( [paragraph.get('content') for paragraph in result if paragraph.get('is_hit_handling_method')]), 'question': question}, {}) @staticmethod def reset_paragraph(paragraph: Dict, embedding_list: List): filter_embedding_list = [embedding for embedding in embedding_list if str(embedding.get('paragraph_id')) == str(paragraph.get('id'))] if filter_embedding_list is not None and len(filter_embedding_list) > 0: find_embedding = filter_embedding_list[-1] return { **paragraph, 'similarity': find_embedding.get('similarity'), 'is_hit_handling_method': find_embedding.get('similarity') > paragraph.get( 'directly_return_similarity') and paragraph.get('hit_handling_method') == 'directly_return', 'update_time': paragraph.get('update_time').strftime("%Y-%m-%d %H:%M:%S"), 'create_time': paragraph.get('create_time').strftime("%Y-%m-%d %H:%M:%S"), 'id': str(paragraph.get('id')), 'knowledge_id': str(paragraph.get('knowledge_id')), 'document_id': str(paragraph.get('document_id')), 'meta': reset_meta(paragraph.get('meta')) } @staticmethod def list_paragraph(embedding_list: List, vector): paragraph_id_list = [row.get('paragraph_id') for row in embedding_list] if paragraph_id_list is None or len(paragraph_id_list) == 0: return [] paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list), get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_knowledge_paragraph_by_paragraph_id.sql')), with_table_name=True) # 如果向量库中存在脏数据 直接删除 if len(paragraph_list) != len(paragraph_id_list): exist_paragraph_list = [row.get('id') for row in paragraph_list] for paragraph_id in paragraph_id_list: if not exist_paragraph_list.__contains__(paragraph_id): vector.delete_by_paragraph_id(paragraph_id) return paragraph_list def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), 'show_knowledge': self.context.get('show_knowledge'), 'question': self.context.get('question'), "index": index, 'run_time': self.context.get('run_time'), 'paragraph_list': self.context.get('paragraph_list'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/speech_to_text_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class SpeechToTextNodeSerializer(serializers.Serializer): stt_model_id = serializers.CharField(required=True, label=_("Model id")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) audio_list = serializers.ListField(required=True, label=_("The audio file cannot be empty")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) class ISpeechToTextNode(INode): type = 'speech-to-text-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP,WorkflowMode.TOOL,WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return SpeechToTextNodeSerializer def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('audio_list')[0], self.node_params_serializer.data.get('audio_list')[1:]) for audio in res: if 'file_id' not in audio: raise ValueError( _("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails")) return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, stt_model_id, audio, model_params_setting=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/speech_to_text_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_speech_to_text_node import BaseSpeechToTextNode ================================================ FILE: apps/application/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py ================================================ # coding=utf-8 import os import tempfile from concurrent.futures import ThreadPoolExecutor from django.db.models import QuerySet from application.flow.i_step_node import NodeResult from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode from common.utils.common import split_and_transcribe, any_to_mp3 from knowledge.models import File from models_provider.tools import get_model_instance_by_model_workspace_id class BaseSpeechToTextNode(ISpeechToTextNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['result'] = details.get('answer') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') self.context['exception_message'] = details.get('err_message') def execute(self, stt_model_id, audio, model_params_setting=None, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') stt_model = get_model_instance_by_model_workspace_id(stt_model_id, workspace_id, **(model_params_setting or {})) audio_list = audio self.context['audio_list'] = audio def process_audio_item(audio_item, model): file = QuerySet(File).filter(id=audio_item['file_id']).first() # 根据file_name 吧文件转成mp3格式 file_format = file.file_name.split('.')[-1] with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file: temp_file.write(file.get_bytes()) temp_file_path = temp_file.name with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file: temp_mp3_path = temp_amr_file.name any_to_mp3(temp_file_path, temp_mp3_path) try: transcription = split_and_transcribe(temp_mp3_path, model) return {file.file_name: transcription} finally: os.remove(temp_file_path) os.remove(temp_mp3_path) def process_audio_items(audio_list, model): with ThreadPoolExecutor(max_workers=5) as executor: results = list(executor.map(lambda item: process_audio_item(item, model), audio_list)) return results result = process_audio_items(audio_list, stt_model) content = [] result_content = [] for item in result: for key, value in item.items(): content.append(f'### {key}\n{value}') result_content.append(value) return NodeResult({'answer': '\n'.join(result_content), 'result': '\n'.join(result_content), 'content': content}, {}) def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'answer': self.context.get('answer'), 'content': self.context.get('content'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'audio_list': self.context.get('audio_list'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/start_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:30 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/start_node/i_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_start_node.py @date:2024/6/3 16:54 @desc: """ from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class IStarNode(INode): type = 'start-node' support = [WorkflowMode.APPLICATION] def _run(self): return self.execute(**self.flow_params_serializer.data) def execute(self, question, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/start_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:36 @desc: """ from .base_start_node import BaseStartStepNode ================================================ FILE: apps/application/flow/step_node/start_node/impl/base_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_start_node.py @date:2024/6/3 17:17 @desc: """ import time from datetime import datetime from typing import List, Type from django.utils import timezone from rest_framework import serializers from application.flow.i_step_node import NodeResult from application.flow.step_node.start_node.i_start_node import IStarNode def get_default_global_variable(input_field_list: List): return { item.get('variable') or item.get('field'): item.get('default_value') for item in input_field_list if item.get('default_value', None) is not None } def get_global_variable(node): body = node.workflow_manage.get_body() history_chat_record = node.flow_params_serializer.data.get('history_chat_record', []) history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in history_chat_record] chat_id = node.flow_params_serializer.data.get('chat_id') return {'time': timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(), 'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data, 'chat_user_id': body.get('chat_user_id'), 'chat_user_type': body.get('chat_user_type'), 'chat_user': body.get('chat_user'), 'chat_user_group': body.get('chat_user_group') } class BaseStartStepNode(IStarNode): def save_context(self, details, workflow_manage): base_node = self.workflow_manage.get_base_node() default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', [])) default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', [])) workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)} self.context['question'] = details.get('question') self.context['run_time'] = details.get('run_time') self.context['document'] = details.get('document_list') self.context['image'] = details.get('image_list') self.context['audio'] = details.get('audio_list') self.context['video'] = details.get('video_list') self.context['other'] = details.get('other_list') self.context['exception_message'] = details.get('err_message') self.status = details.get('status') self.err_message = details.get('err_message') for key, value in workflow_variable.items(): workflow_manage.context[key] = value for item in details.get('global_fields', []): workflow_manage.context[item.get('key')] = item.get('value') self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable() def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: pass def execute(self, question, **kwargs) -> NodeResult: base_node = self.workflow_manage.get_base_node() default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', [])) default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', [])) workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)} """ 开始节点 初始化全局变量 """ node_variable = { 'question': question, 'image': self.workflow_manage.image_list, 'document': self.workflow_manage.document_list, 'audio': self.workflow_manage.audio_list, 'video': self.workflow_manage.video_list, 'other': self.workflow_manage.other_list, } self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable() return NodeResult(node_variable, workflow_variable) def get_details(self, index: int, **kwargs): global_fields = [] for field in self.node.properties.get('config')['globalFields']: key = field['value'] global_fields.append({ 'label': field['label'], 'key': key, 'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else '' }) return { 'name': self.node.properties.get('stepName'), "index": index, "question": self.context.get('question'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'image_list': self.context.get('image'), 'video_list': self.context.get('video'), 'document_list': self.context.get('document'), 'audio_list': self.context.get('audio'), 'other_list': self.context.get('other'), 'global_fields': global_fields, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/text_to_speech_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py ================================================ # coding=utf-8 from typing import Type from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class TextToSpeechNodeSerializer(serializers.Serializer): tts_model_id = serializers.CharField(required=True, label=_("Model id")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) content_list = serializers.ListField(required=True, label=_("Text content")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) class ITextToSpeechNode(INode): type = 'text-to-speech-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return TextToSpeechNodeSerializer def _run(self): content = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('content_list')[0], self.node_params_serializer.data.get('content_list')[1:]) return self.execute(content=content, **self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, tts_model_id, content, model_params_setting=None, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/text_to_speech_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_text_to_speech_node import BaseTextToSpeechNode ================================================ FILE: apps/application/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py ================================================ # coding=utf-8 import io import mimetypes from django.core.files.uploadedfile import InMemoryUploadedFile from application.flow.common import WorkflowMode from application.flow.i_step_node import NodeResult from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode from common.utils.common import _remove_empty_lines from knowledge.models import FileSourceType from models_provider.tools import get_model_instance_by_model_workspace_id from oss.serializers.file import FileSerializer from pydub import AudioSegment def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"): content_type, _ = mimetypes.guess_type(file_name) if content_type is None: # 如果未能识别,设置为默认的二进制文件类型 content_type = "application/octet-stream" # 创建一个内存中的字节流对象 file_stream = io.BytesIO(file_bytes) # 获取文件大小 file_size = len(file_bytes) uploaded_file = InMemoryUploadedFile( file=file_stream, field_name=None, name=file_name, content_type=content_type, size=file_size, charset=None, ) return uploaded_file class BaseTextToSpeechNode(ITextToSpeechNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['result'] = details.get('result') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, tts_model_id, content, model_params_setting=None, max_length=1024, **kwargs) -> NodeResult: # 分割文本为合理片段 content = _remove_empty_lines(content) content_chunks = [content[i:i + max_length] for i in range(0, len(content), max_length)] # 生成并收集所有音频片段 audio_segments = [] temp_files = [] for i, chunk in enumerate(content_chunks): self.context['content'] = chunk workspace_id = self.workflow_manage.get_body().get('workspace_id') model = get_model_instance_by_model_workspace_id( tts_model_id, workspace_id, **model_params_setting) audio_byte = model.text_to_speech(chunk) # 保存为临时音频文件用于合并 temp_file = io.BytesIO(audio_byte) audio_segment = AudioSegment.from_file(temp_file) audio_segments.append(audio_segment) temp_files.append(temp_file) # 合并所有音频片段 combined_audio = AudioSegment.empty() for segment in audio_segments: combined_audio += segment # 将合并后的音频转为字节流 output_buffer = io.BytesIO() combined_audio.export(output_buffer, format="mp3") combined_bytes = output_buffer.getvalue() file_name = 'combined_audio.mp3' file = bytes_to_uploaded_file(combined_bytes, file_name) # 存储合并后的音频文件 file_url = self.upload_file(file) # 生成音频标签 audio_label = f'' file_id = file_url.split('/')[-1] audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}] # 关闭所有临时文件 for temp_file in temp_files: temp_file.close() output_buffer.close() return NodeResult({ 'answer': audio_label, 'result': audio_list }, {}) def upload_file(self, file): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.upload_knowledge_file(file) return self.upload_application_file(file) def upload_knowledge_file(self, file): knowledge_id = self.workflow_params.get('knowledge_id') meta = { 'debug': False, 'knowledge_id': knowledge_id, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': knowledge_id, 'source_type': FileSourceType.KNOWLEDGE.value }).upload() return file_url def upload_application_file(self, file): application = self.workflow_manage.work_flow_post_handler.chat_info.application chat_id = self.workflow_params.get('chat_id') meta = { 'debug': False if application.id else True, 'chat_id': chat_id, 'application_id': str(application.id) if application.id else None, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': meta['application_id'], 'source_type': FileSourceType.APPLICATION.value }).upload() return file_url 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, 'status': self.status, 'content': self.context.get('content'), 'err_message': self.err_message, 'answer': self.context.get('answer'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/text_to_video_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class TextToVideoNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) prompt = serializers.CharField(required=True, label=_("Prompt word (positive)")) negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"), allow_null=True, allow_blank=True, ) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=False, default=0, label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=False, default='NODE', label=_("Conversation storage type")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model parameter settings")) class ITextToVideoNode(INode): type = 'text-to-video-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return TextToVideoNodeSerializer def _run(self): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/text_to_video_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_text_to_video_node import BaseTextToVideoNode ================================================ FILE: apps/application/flow/step_node/text_to_video_step_node/impl/base_text_to_video_node.py ================================================ # coding=utf-8 from functools import reduce from typing import List import requests from langchain_core.messages import BaseMessage, HumanMessage, AIMessage from application.flow.common import WorkflowMode from application.flow.i_step_node import NodeResult from application.flow.step_node.text_to_video_step_node.i_text_to_video_node import ITextToVideoNode from common.utils.common import bytes_to_uploaded_file from knowledge.models import FileSourceType from oss.serializers.file import FileSerializer from models_provider.tools import get_model_instance_by_model_workspace_id from django.utils.translation import gettext class BaseTextToVideoNode(ITextToVideoNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['exception_message'] = details.get('err_message') self.context['question'] = details.get('question') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, model_params_setting, chat_record_id, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') ttv_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question message_list = self.generate_message_list(question, history_message) self.context['message_list'] = message_list self.context['dialogue_type'] = dialogue_type self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt) video_urls = ttv_model.generate_video(question, negative_prompt) # 保存图片 if video_urls is None: return NodeResult({'answer': gettext('Failed to generate video')}, {}) file_name = 'generated_video.mp4' if isinstance(video_urls, str) and video_urls.startswith('http'): video_urls = requests.get(video_urls).content file = bytes_to_uploaded_file(video_urls, file_name) file_url = self.upload_file(file) video_label = f'' video_list = [{'file_id': file_url.split('/')[-1], 'file_name': file_name, 'url': file_url}] return NodeResult({'answer': video_label, 'chat_model': ttv_model, 'message_list': message_list, 'video': video_list, 'history_message': history_message, 'question': question}, {}) def upload_file(self, file): if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.upload_knowledge_file(file) return self.upload_application_file(file) def upload_knowledge_file(self, file): knowledge_id = self.workflow_params.get('knowledge_id') meta = { 'debug': False, 'knowledge_id': knowledge_id } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': knowledge_id, 'source_type': FileSourceType.KNOWLEDGE.value }).upload() return file_url def upload_application_file(self, file): application = self.workflow_manage.work_flow_post_handler.chat_info.application chat_id = self.workflow_params.get('chat_id') meta = { 'debug': False if application.id else True, 'chat_id': chat_id, 'application_id': str(application.id) if application.id else None, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': meta['application_id'], 'source_type': FileSourceType.APPLICATION.value }).upload() return file_url def generate_history_ai_message(self, chat_record): for val in chat_record.details.values(): if self.node.id == val['node_id'] and 'image_list' in val: if val['dialogue_type'] == 'WORKFLOW': return chat_record.get_ai_message() image_list = val['image_list'] return AIMessage(content=[ *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list] ]) return chat_record.get_ai_message() def get_history_message(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_human_message(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'image_list' in data: image_list = data['image_list'] if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) return HumanMessage(content=data['question']) return HumanMessage(content=chat_record.problem_text) def generate_prompt_question(self, prompt): return self.workflow_manage.generate_prompt(prompt) def generate_message_list(self, question: str, history_message): return [ *history_message, question ] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'image_list': self.context.get('image_list'), 'dialogue_type': self.context.get('dialogue_type'), 'negative_prompt': self.context.get('negative_prompt'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/tool_lib_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/8/8 17:45 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_function_lib_node.py @date:2024/8/8 16:21 @desc: """ from typing import Type from django.db import connection from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.field.common import ObjectField from tools.models.tool import Tool class InputField(serializers.Serializer): name = serializers.CharField(required=True, label=_('Variable Name')) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list]) class FunctionLibNodeParamsSerializer(serializers.Serializer): tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID')) input_field_list = InputField(required=True, many=True) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id')).first() # 归还链接到连接池 connection.close() if f_lib is None: raise Exception(_('The function has been deleted')) class IToolLibNode(INode): type = 'tool-lib-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FunctionLibNodeParamsSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/tool_lib_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/8/8 17:48 @desc: """ from .base_tool_lib_node import BaseToolLibNodeNode ================================================ FILE: apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_function_lib_node.py @date:2024/8/8 17:49 @desc: """ import base64 import io import json import mimetypes import time import traceback from typing import Dict import uuid_utils.compat as uuid from django.core.files.uploadedfile import InMemoryUploadedFile from django.db.models import QuerySet from django.utils.translation import gettext as _ from application.flow.i_step_node import NodeResult from application.flow.step_node.tool_lib_node.i_tool_lib_node import IToolLibNode from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException from common.utils.logger import maxkb_logger from common.utils.rsa_util import rsa_long_decrypt from common.utils.tool_code import ToolExecutor from knowledge.models import FileSourceType from knowledge.models.knowledge_action import State from oss.serializers.file import FileSerializer from tools.models import Tool, ToolRecord, ToolTaskTypeChoices function_executor = ToolExecutor() def write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable is not None: for key in step_variable: node.context[key] = step_variable[key] if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable: result = str(step_variable['result']) + '\n' yield result node.answer_text = result node.context['run_time'] = time.time() - node.context['start_time'] def get_field_value(debug_field_list, name, is_required): result = [field for field in debug_field_list if field.get('name') == name] if len(result) > 0: return result[-1]['value'] if is_required: raise AppApiException(500, _('Field: {name} No value set').format(name=name)) return None def valid_reference_value(_type, value, name): try: if _type == 'int': instance_type = int | float elif _type == 'boolean': instance_type = bool elif _type == 'float': instance_type = float | int elif _type == 'dict': value = json.loads(value) if isinstance(value, str) else value instance_type = dict elif _type == 'array': value = json.loads(value) if isinstance(value, str) else value instance_type = list elif _type == 'string': instance_type = str else: raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Unsupported types' ).format(name=name, _type=_type)) except: return value if not isinstance(value, instance_type): raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Type error' ).format(name=name, _type=_type, value=value)) return value def convert_value(name: str, value, _type, is_required, source, node): if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)): return None if source == 'reference': value = node.workflow_manage.get_reference_field( value[0], value[1:]) if value is None: if not is_required: return None else: raise Exception(_( 'Field: {name} Type: {_type} is required' ).format(name=name, _type=_type)) value = valid_reference_value(_type, value, name) if _type == 'int': return int(value) if _type == 'float': return float(value) return value try: value = node.workflow_manage.generate_prompt(value) if _type == 'int': return int(value) if _type == 'boolean': value = 0 if ['0', '[]'].__contains__(value) else value return bool(value) if _type == 'float': return float(value) if _type == 'dict': v = json.loads(value) if isinstance(v, dict): return v raise Exception(_('type error')) if _type == 'array': v = json.loads(value) if isinstance(v, list): return v raise Exception(_('type error')) return value except Exception as e: raise Exception( _('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type, value=value)) def valid_function(tool_lib, workspace_id): if tool_lib is None: raise Exception(_('Tool does not exist')) get_authorized_tool = DatabaseModelManage.get_model("get_authorized_tool") if tool_lib and tool_lib.workspace_id != workspace_id and get_authorized_tool is not None: tool_lib = get_authorized_tool(QuerySet(Tool).filter(id=tool_lib.id), workspace_id).first() if tool_lib is None: raise Exception(_("Tool does not exist")) if not tool_lib.is_active: raise Exception(_("Tool is not active")) def _filter_file_bytes(data): """递归过滤掉所有层级的 file_bytes""" if isinstance(data, dict): return {k: _filter_file_bytes(v) for k, v in data.items() if k != 'file_bytes'} elif isinstance(data, list): return [_filter_file_bytes(item) for item in data] else: return data def bytes_to_uploaded_file(file_bytes, file_name="unknown"): content_type, _ = mimetypes.guess_type(file_name) if content_type is None: # 如果未能识别,设置为默认的二进制文件类型 content_type = "application/octet-stream" # 创建一个内存中的字节流对象 file_stream = io.BytesIO(file_bytes) # 获取文件大小 file_size = len(file_bytes) uploaded_file = InMemoryUploadedFile( file=file_stream, field_name=None, name=file_name, content_type=content_type, size=file_size, charset=None, ) return uploaded_file def _get_result_detail(result): if isinstance(result, dict): result_dict = {k: (str(v)[:500] if len(str(v)) > 500 else v) for k, v in result.items()} elif isinstance(result, list): result_dict = [str(item)[:500] if len(str(item)) > 500 else item for item in result] elif isinstance(result, str): result_dict = result[:500] if len(result) > 500 else result else: result_dict = result return result_dict class BaseToolLibNodeNode(IToolLibNode): def save_context(self, details, workflow_manage): self.context['result'] = details.get('result') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result'): self.answer_text = str(details.get('result')) def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') tool_lib = QuerySet(Tool).filter(id=tool_lib_id).first() valid_function(tool_lib, workspace_id) params = { field.get('name'): convert_value( field.get('name'), field.get('value'), field.get('type'), field.get('is_required'), field.get('source'), self ) for field in [ { 'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'), ), **field } for field in tool_lib.input_field_list ] } self.context['params'] = params # 合并初始化参数 init_params_default_value = {i["field"]: i.get('default_value') for i in tool_lib.init_field_list} if tool_lib.init_params is not None: all_params = init_params_default_value | json.loads(rsa_long_decrypt(tool_lib.init_params)) | params else: all_params = init_params_default_value | params if self.node.properties.get('kind') == 'data-source': exist = function_executor.exec_code( f'{tool_lib.code}\ndef function_exist(function_name): return callable(globals().get(function_name))', {'function_name': 'get_download_file_list'}) all_params = {**all_params, **self.workflow_params.get('data_source')} if exist: download_file_list = [] download_list = function_executor.exec_code(tool_lib.code, all_params, function_name='get_download_file_list') for item in download_list: result = function_executor.exec_code(tool_lib.code, {**all_params, 'download_item': item}, function_name='download') file_bytes = result.get('file_bytes', []) chunks = [] for chunk in file_bytes: chunks.append(base64.b64decode(chunk)) file = bytes_to_uploaded_file(b''.join(chunks), result.get('name')) file_url = self.upload_knowledge_file(file) download_file_list.append({'file_id': file_url.split('/')[-1], 'name': result.get('name')}) result = download_file_list else: result = function_executor.exec_code(tool_lib.code, all_params) else: result = self.tool_exec_record(tool_lib, all_params) return NodeResult({'result': result}, (self.workflow_manage.params.get('knowledge_base') or {}) if self.node.properties.get( 'kind') == 'data-source' else {}, _write_context=write_context) def tool_exec_record(self, tool_lib, all_params): task_record_id = uuid.uuid7() start_time = time.time() try: # 过滤掉 tool_init_params 中的参数 tool_init_params = json.loads(rsa_long_decrypt(tool_lib.init_params)) if tool_lib.init_params else {} if tool_init_params: filtered_args = { k: v for k, v in all_params.items() if k not in tool_init_params } else: filtered_args = all_params ToolRecord( id=task_record_id, workspace_id=tool_lib.workspace_id, tool_id=tool_lib.id, source_type=ToolTaskTypeChoices.KNOWLEDGE.value if self.workflow_manage.params.get( 'knowledge_id') else ToolTaskTypeChoices.APPLICATION.value, source_id=self.workflow_manage.params.get('knowledge_id') or self.workflow_manage.params.get( 'application_id'), meta={'input': filtered_args, 'output': {}}, state=State.STARTED ).save() result = function_executor.exec_code(tool_lib.code, all_params) result_dict = _get_result_detail(result) QuerySet(ToolRecord).filter(id=task_record_id).update( state=State.SUCCESS, run_time=time.time() - start_time, meta={'input': filtered_args, 'output': result_dict} ) return result except Exception as e: maxkb_logger.error(f"Tool execution error: {traceback.format_exc()}") QuerySet(ToolRecord).filter(id=task_record_id).update( state=State.FAILURE, run_time=time.time() - start_time, meta={'input': filtered_args, 'output': 'Error: ' + str(e)} ) def upload_knowledge_file(self, file): knowledge_id = self.workflow_params.get('knowledge_id') meta = { 'debug': False, 'knowledge_id': knowledge_id, } file_url = FileSerializer(data={ 'file': file, 'meta': meta, 'source_id': knowledge_id, 'source_type': FileSourceType.KNOWLEDGE.value }).upload().replace("./oss/file/", '') file.close() return file_url def get_details(self, index: int, **kwargs): result = _filter_file_bytes(self.context.get('result')) return { 'name': self.node.properties.get('stepName'), "index": index, "result": result, "params": self.context.get('params'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/tool_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/8/13 10:43 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/tool_node/i_tool_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_function_lib_node.py @date:2024/8/8 16:21 @desc: """ import re from typing import Type from django.core import validators from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.utils.formatting import lazy_format from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.exception.app_exception import AppApiException from common.field.common import ObjectField class InputField(serializers.Serializer): name = serializers.CharField(required=True, label=_('Variable Name')) is_required = serializers.BooleanField(required=True, label=_("Is this field required")) type = serializers.CharField(required=True, label=_("type"), validators=[ validators.RegexValidator(regex=re.compile("^string|int|dict|array|float|boolean$"), message=_("The field only supports string|int|dict|array|float"), code=500) ]) source = serializers.CharField(required=True, label=_("source"), validators=[ validators.RegexValidator(regex=re.compile("^custom|reference$"), message=_("The field only supports custom|reference"), code=500) ]) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list]) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) is_required = self.data.get('is_required') if is_required and self.data.get('value') is None: message = lazy_format(_('{field}, this field is required.'), field=self.data.get("name")) raise AppApiException(500, message) class FunctionNodeParamsSerializer(serializers.Serializer): input_field_list = InputField(required=True, many=True) code = serializers.CharField(required=True, label=_("function")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) class IToolNode(INode): type = 'tool-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FunctionNodeParamsSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, input_field_list, code, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/tool_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/8/13 11:19 @desc: """ from .base_tool_node import BaseToolNodeNode ================================================ FILE: apps/application/flow/step_node/tool_node/impl/base_tool_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_function_lib_node.py @date:2024/8/8 17:49 @desc: """ import json import time from typing import Dict from django.utils.translation import gettext as _ from application.flow.i_step_node import NodeResult from application.flow.step_node.tool_node.i_tool_node import IToolNode from common.utils.tool_code import ToolExecutor from maxkb.const import CONFIG function_executor = ToolExecutor() def write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable is not None: for key in step_variable: node.context[key] = step_variable[key] if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable: result = str(step_variable['result']) + '\n' yield result node.answer_text = result node.context['run_time'] = time.time() - node.context['start_time'] def valid_reference_value(_type, value, name): try: if _type == 'int': instance_type = int | float elif _type == 'boolean': instance_type = bool elif _type == 'float': instance_type = float | int elif _type == 'dict': value = json.loads(value) if isinstance(value, str) else value instance_type = dict elif _type == 'array': value = json.loads(value) if isinstance(value, str) else value instance_type = list elif _type == 'string': instance_type = str else: raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Unsupported types' ).format(name=name, _type=_type)) except: return value if not isinstance(value, instance_type): raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Type error' ).format(name=name, _type=_type, value=value)) return value def convert_value(name: str, value, _type, is_required, source, node): if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)): return None if source == 'reference': value = node.workflow_manage.get_reference_field( value[0], value[1:]) if value is None: if not is_required: return None else: raise Exception(_( 'Field: {name} Type: {_type} is required' ).format(name=name, _type=_type)) value = valid_reference_value(_type, value, name) if _type == 'int': return int(value) if _type == 'float': return float(value) return value try: value = node.workflow_manage.generate_prompt(value) if _type == 'int': return int(value) if _type == 'boolean': value = 0 if ['0', '[]'].__contains__(value) else value return bool(value) if _type == 'float': return float(value) if _type == 'dict': v = json.loads(value) if isinstance(v, dict): return v raise Exception(_('type error')) if _type == 'array': v = json.loads(value) if isinstance(v, list): return v raise Exception(_('type error')) return value except Exception as e: raise Exception( _('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type, value=value)) class BaseToolNodeNode(IToolNode): def save_context(self, details, workflow_manage): self.context['result'] = details.get('result') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = str(details.get('result')) def execute(self, input_field_list, code, **kwargs) -> NodeResult: params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'), field.get('is_required'), field.get('source'), self) for field in input_field_list} result = function_executor.exec_code(code, params) self.context['params'] = params return NodeResult({'result': result}, {}, _write_context=write_context) def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, "result": self.context.get('result'), "params": self.context.get('params'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/tool_start_node/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:30 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/tool_start_node/i_tool_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: i_start_node.py @date:2024/6/3 16:54 @desc: """ from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class IToolStartNode(INode): type = 'tool-start-node' support = [WorkflowMode.TOOL] def _run(self): return self.execute(**self.flow_params_serializer.data) def execute(self, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/tool_start_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 15:36 @desc: """ from .base_tool_start_node import BaseToolStartStepNode ================================================ FILE: apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_start_node.py @date:2024/6/3 17:17 @desc: """ from typing import Type from rest_framework import serializers from application.flow.i_step_node import NodeResult from application.flow.step_node.tool_start_node.i_tool_start_node import IToolStartNode class BaseToolStartStepNode(IToolStartNode): def save_context(self, details, workflow_manage): base_node = self.workflow_manage.get_base_node() workflow_variable = {} self.context['exception_message'] = details.get('err_message') self.status = details.get('status') self.err_message = details.get('err_message') for key, value in workflow_variable.items(): workflow_manage.context[key] = value for item in details.get('global_fields', []): workflow_manage.context[item.get('key')] = item.get('value') def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: pass def execute(self, **kwargs) -> NodeResult: base_node = self.workflow_manage.get_base_node() global_value = {} params = self.workflow_manage.get_body() for item in base_node.properties.get('user_input_field_list', []): global_value[item.get('field')] = params[item.get('field')] self.workflow_manage.out_context = { item.get('field'): None for item in base_node.properties.get('user_output_field_list', []) if item.get('default_value', None) is not None } return NodeResult({}, global_value) def get_details(self, index: int, **kwargs): global_fields = [] for field in self.node.properties.get('config')['globalFields']: key = field['value'] global_fields.append({ 'label': field['label'], 'key': key, 'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else '' }) return { 'name': self.node.properties.get('stepName'), "index": index, "question": self.context.get('question'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'err_message': self.err_message, 'global_fields': global_fields, '': '', 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/tool_workflow_lib_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2026/3/16 13:53 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_function_lib_node.py @date:2024/8/8 16:21 @desc: """ from typing import Type from django.db import connection from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult from common.field.common import ObjectField from tools.models.tool import Tool, ToolType class InputField(serializers.Serializer): field = serializers.CharField(required=True, label=_('Variable Name')) label = serializers.CharField(required=True, label=_('Variable Label')) source = serializers.CharField(required=True, label=_('Variable Source')) type = serializers.CharField(required=True, label=_('Variable Type')) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list, bool, dict, int, float]) class FunctionLibNodeParamsSerializer(serializers.Serializer): tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID')) input_field_list = InputField(required=True, many=True) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id'), tool_type=ToolType.WORKFLOW).first() # 归还链接到连接池 connection.close() if f_lib is None: raise Exception(_('The function has been deleted')) class IToolWorkflowLibNode(INode): type = 'tool-workflow-lib-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return FunctionLibNodeParamsSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2026/3/16 13:53 @desc: """ from .base_tool_workflow_lib_node import * ================================================ FILE: apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_tool_workflow_lib_node.py.py @date:2026/3/16 13:55 @desc: """ import time from typing import Dict import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from application.flow.common import WorkflowMode, Workflow from application.flow.i_step_node import NodeResult, ToolWorkflowPostHandler, INode from application.flow.step_node.tool_workflow_lib_node.i_tool_workflow_lib_node import IToolWorkflowLibNode from application.models import ChatRecord from application.serializers.common import ToolExecute from common.exception.app_exception import ChatException from common.handle.impl.response.loop_to_response import LoopToResponse from tools.models import ToolWorkflowVersion def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, reasoning_content: str): result = node_variable.get('result') node.context['application_node_dict'] = node_variable.get('application_node_dict') node.context['node_dict'] = node_variable.get('node_dict', {}) node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec') node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0) node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0) node.context['answer'] = answer node.context['result'] = answer node.context['reasoning_content'] = reasoning_content node.context['run_time'] = time.time() - node.context['start_time'] if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def get_answer_list(instance, child_node_node_dict, runtime_node_id): answer_list = instance.get_record_answer_list() for a in answer_list: _v = child_node_node_dict.get(a.get('runtime_node_id')) if _v: a['runtime_node_id'] = runtime_node_id a['child_node'] = _v return answer_list def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ workflow_manage_new_instance = node_variable.get('workflow_manage_new_instance') node_params = node.node_params start_node_id = node_params.get('child_node', {}).get('runtime_node_id') child_node_data = node.context.get('child_node_data') or [] start_node_data = None chat_record = None child_node = None if start_node_id: chat_record_id = node_params.get('child_node', {}).get('chat_record_id') child_node = node_params.get('child_node', {}).get('child_node') start_node_data = node_params.get('node_data') chat_record = ChatRecord(id=chat_record_id, answer_text_list=[], answer_text='', details=child_node_data) instance = workflow_manage_new_instance(start_node_id, start_node_data, chat_record, child_node) answer = '' reasoning_content = '' usage = {} node_child_node = {} is_interrupt_exec = False response = instance.stream() child_node_node_dict = {} for chunk in response: response_content = chunk content = (response_content.get('content', '') or '') runtime_node_id = response_content.get('runtime_node_id', '') chat_record_id = response_content.get('chat_record_id', '') child_node = response_content.get('child_node') node_type = response_content.get('node_type') _reasoning_content = (response_content.get('reasoning_content', '') or '') if node_type == 'form-node': is_interrupt_exec = True answer += content reasoning_content += _reasoning_content node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'child_node': child_node} child_node = chunk.get('child_node') runtime_node_id = chunk.get('runtime_node_id', '') chat_record_id = chunk.get('chat_record_id', '') child_node_node_dict[runtime_node_id] = { 'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'child_node': child_node} content_chunk = (chunk.get('content', '') or '') reasoning_content_chunk = (chunk.get('reasoning_content', '') or '') reasoning_content += reasoning_content_chunk answer += content_chunk yield chunk if chunk.get('node_status', "SUCCESS") == 'ERROR': is_interrupt_exec = True node.status = 500 node.err_message = chunk.get('content') usage = response_content.get('usage', {}) child_answer_data = get_answer_list(instance, child_node_node_dict, node.runtime_node_id) node.context['usage'] = {'usage': usage} node.context['child_node'] = node_child_node node.context['child_node_data'] = instance.get_runtime_details() node.context['is_interrupt_exec'] = is_interrupt_exec node.context['child_node_data'] = instance.get_runtime_details() node.context['child_answer_data'] = child_answer_data node.context['run_time'] = time.time() - node.context.get("start_time") for key, value in instance.out_context.items(): node.context[key] = value def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict): return node.context.get('is_interrupt_exec', False) class BaseToolWorkflowLibNodeNode(IToolWorkflowLibNode): def get_parameters(self, input_field_list): result = {} for input in input_field_list: source = input.get('source') value = input.get('value') if source == 'reference': value = self.workflow_manage.get_reference_field( value[0], value[1:]) result[input.get('field')] = value return result def save_context(self, details, workflow_manage): self.context['child_answer_data'] = details.get('child_answer_data') self.context['child_node_data'] = details.get('child_node_data') self.context['result'] = details.get('result') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result'): self.answer_text = str(details.get('result')) @staticmethod def to_chat_record(record): if record is None: return None return ChatRecord( answer_text_list=record.meta.get('answer_text_list'), details=record.meta.get('details'), answer_text='', ) def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: from application.flow.tool_workflow_manage import ToolWorkflowManage workspace_id = self.workflow_manage.get_body().get('workspace_id') tool_workflow_version = QuerySet(ToolWorkflowVersion).filter(tool_id=tool_lib_id).order_by( '-create_time')[0:1].first() if tool_workflow_version is None: raise ChatException(500, _("The tool has not been published. Please use it after publishing.")) parameters = self.get_parameters(input_field_list) tool_record_id = (self.node_params.get('child_node') or {}).get('chat_record_id') or str(uuid.uuid7()) took_execute = ToolExecute(tool_lib_id, tool_record_id, workspace_id, self.workflow_manage.get_source_type(), self.workflow_manage.get_source_id(), False) def workflow_manage_new_instance(start_node_id=None, start_node_data=None, chat_record=None, child_node=None): work_flow_manage = ToolWorkflowManage( Workflow.new_instance(tool_workflow_version.work_flow, WorkflowMode.TOOL), { 'chat_record_id': tool_record_id, 'tool_id': tool_lib_id, 'stream': True, 'workspace_id': workspace_id, **parameters}, ToolWorkflowPostHandler(took_execute, tool_lib_id), base_to_response=LoopToResponse(), start_node_id=start_node_id, start_node_data=start_node_data, child_node=child_node, chat_record=self.to_chat_record(took_execute.get_record()), is_the_task_interrupted=lambda: False) return work_flow_manage return NodeResult({'workflow_manage_new_instance': workflow_manage_new_instance}, {}, _write_context=write_context_stream, _is_interrupt=_is_interrupt_exec) def get_details(self, index: int, **kwargs): result = self.context.get('result') return { 'name': self.node.properties.get('stepName'), "index": index, "result": result, "params": self.context.get('params'), 'run_time': self.context.get('run_time'), 'type': self.node.type, 'status': self.status, 'child_node_data': self.context.get("child_node_data"), 'child_answer_data': self.context.get("child_answer_data"), 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/variable_aggregation_node/__init__.py ================================================ ================================================ FILE: apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class VariableListSerializer(serializers.Serializer): v_id = serializers.CharField(required=True, label=_("Variable id")) variable = serializers.ListField(required=True, label=_("Variable")) class VariableGroupSerializer(serializers.Serializer): id = serializers.CharField(required=True, label=_("Group id")) field = serializers.CharField(required=True, label=_("group_name")) label = serializers.CharField(required=True) variable_list = VariableListSerializer(many=True) class VariableAggregationNodeSerializer(serializers.Serializer): strategy = serializers.CharField(required=True, label=_("Strategy")) group_list = VariableGroupSerializer(many=True) class IVariableAggregation(INode): type = 'variable-aggregation-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableAggregationNodeSerializer def _run(self): return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, strategy, group_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/variable_aggregation_node/impl/__init__.py ================================================ ================================================ FILE: apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎² @file: base_variable_aggregation_node.py @date:2025/10/23 17:42 @desc: """ from application.flow.i_step_node import NodeResult from application.flow.step_node.variable_aggregation_node.i_variable_aggregation_node import IVariableAggregation def _filter_file_bytes(data): """递归过滤掉所有层级的 file_bytes""" if isinstance(data, dict): return {k: _filter_file_bytes(v) for k, v in data.items() if k != 'file_bytes'} elif isinstance(data, list): return [_filter_file_bytes(item) for item in data] else: return data class BaseVariableAggregationNode(IVariableAggregation): def save_context(self, details, workflow_manage): for key, value in details.get('result').items(): self.context['key'] = value self.context['result'] = details.get('result') self.context['strategy'] = details.get('strategy') self.context['group_list'] = details.get('group_list') self.context['exception_message'] = details.get('err_message') def get_first_non_null(self, variable_list): for variable in variable_list: v = self.workflow_manage.get_reference_field( variable.get('variable')[0], variable.get('variable')[1:]) if v is not None and not (isinstance(v, (str, list, dict)) and len(v) == 0): return v return None def set_variable_to_json(self, variable_list): return [self.workflow_manage.get_reference_field( variable.get('variable')[0], variable.get('variable')[1:]) for variable in variable_list] def reset_variable(self, variable): value = self.workflow_manage.get_reference_field( variable.get('variable')[0], variable.get('variable')[1:]) node_id = variable.get('variable')[0] node = self.workflow_manage.flow.get_node(node_id) return {"value": value, 'node_name': node.properties.get('stepName') if node is not None else node_id, 'field': variable.get('variable')[1]} def reset_group_list(self, group_list): result = [] for g in group_list: b = {'label': g.get('label'), 'variable_list': [self.reset_variable(variable) for variable in g.get('variable_list')]} result.append(b) return result def execute(self, strategy, group_list, **kwargs) -> NodeResult: strategy_map = {'first_non_null': self.get_first_non_null, 'variable_to_json': self.set_variable_to_json, } result = {item.get('field'): strategy_map[strategy](item.get('variable_list')) for item in group_list} return NodeResult( {'result': result, 'strategy': strategy, 'group_list': self.reset_group_list(group_list), **result}, {}) def get_details(self, index: int, **kwargs): result = _filter_file_bytes(self.context.get('result')) group_list = _filter_file_bytes(self.context.get('group_list')) return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'type': self.node.type, 'result': result, 'strategy': self.context.get('strategy'), 'group_list': group_list, 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/variable_assign_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class VariableAssignNodeParamsSerializer(serializers.Serializer): variable_list = serializers.ListField(required=True, label=_("Reference Field")) class IVariableAssignNode(INode): type = 'variable-assign-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] 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, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/variable_assign_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/6/11 17:49 @desc: """ from .base_variable_assign_node import * ================================================ FILE: apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py ================================================ # 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') self.context['exception_message'] = details.get('err_message') def global_evaluation(self, variable, value): from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.parentWorkflowManage.context[variable['fields'][1]] = value else: self.workflow_manage.context[variable['fields'][1]] = value def loop_evaluation(self, variable, value): from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.get_loop_context()[variable['fields'][1]] = value def chat_evaluation(self, variable, value): from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.parentWorkflowManage.chat_context[variable['fields'][1]] = value else: self.workflow_manage.chat_context[variable['fields'][1]] = value def out_evaluation(self, variable, value): from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.parentWorkflowManage.out_context[variable['fields'][1]] = value else: self.workflow_manage.out_context[variable['fields'][1]] = value def handle(self, variable, evaluation): result = { 'name': variable['name'], 'input_value': self.get_reference_content(variable['fields']), } if variable['source'] == 'custom': if variable['type'] == 'json': if isinstance(variable['value'], dict) or isinstance(variable['value'], list): val = variable['value'] else: val = json.loads(variable['value']) evaluation(variable, val) result['output_value'] = variable['value'] = val elif variable['type'] == 'string': # 变量解析 例如:{{global.xxx}} val = self.workflow_manage.generate_prompt(variable['value']) evaluation(variable, val) result['output_value'] = val else: val = variable['value'] evaluation(variable, val) result['output_value'] = val else: reference = self.get_reference_content(variable['reference']) evaluation(variable, reference) result['output_value'] = reference return result def execute(self, variable_list, **kwargs) -> NodeResult: # result_list = [] is_chat = False for variable in variable_list: if 'fields' not in variable: continue if 'global' == variable['fields'][0]: result = self.handle(variable, self.global_evaluation) result_list.append(result) if 'chat' == variable['fields'][0]: result = self.handle(variable, self.chat_evaluation) result_list.append(result) is_chat = True if 'loop' == variable['fields'][0]: result = self.handle(variable, self.loop_evaluation) result_list.append(result) if 'output' == variable['fields'][0]: result = self.handle(variable, self.out_evaluation) result_list.append(result) if is_chat: from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.parentWorkflowManage.get_chat_info().set_chat_variable( self.workflow_manage.parentWorkflowManage.chat_context) else: self.workflow_manage.get_chat_info().set_chat_variable(self.workflow_manage.chat_context) return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {}) def get_reference_content(self, fields: List[str]): return 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, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/variable_splitting_node/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/10/13 14:56 @desc: """ from .impl import * ================================================ FILE: apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class VariableSplittingNodeParamsSerializer(serializers.Serializer): input_variable = serializers.ListField(required=True, label=_("input variable")) variable_list = serializers.ListField(required=True, label=_("Split variables")) class IVariableSplittingNode(INode): type = 'variable-splitting-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VariableSplittingNodeParamsSerializer def _run(self): input_variable = self.workflow_manage.get_reference_field( self.node_params_serializer.data.get('input_variable')[0], self.node_params_serializer.data.get('input_variable')[1:]) return self.execute(input_variable, self.node_params_serializer.data['variable_list']) def execute(self, input_variable, variable_list, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/variable_splitting_node/impl/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/10/13 15:01 @desc: """ from .base_variable_splitting_node import * ================================================ FILE: apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_variable_splitting_node.py @date:2025/10/13 15:02 @desc: """ import json from jsonpath_ng import parse from application.flow.i_step_node import NodeResult from application.flow.step_node.variable_splitting_node.i_variable_splitting_node import IVariableSplittingNode def smart_jsonpath_search(data: dict, path: str): """ 智能JSON Path搜索 返回: - 单个匹配: 直接返回值 - 多个匹配: 返回值的列表 - 无匹配: 返回None """ jsonpath_expr = parse(path) matches = jsonpath_expr.find(data) if not matches: return None elif len(matches) == 1: return matches[0].value else: return [match.value for match in matches] class BaseVariableSplittingNode(IVariableSplittingNode): def save_context(self, details, workflow_manage): for key, value in details.get('result').items(): self.context[key] = value self.context['result'] = details.get('result') self.context['request'] = details.get('request') self.context['exception_message'] = details.get('err_message') def execute(self, input_variable, variable_list, **kwargs) -> NodeResult: if type(input_variable).__name__ == "str": try: input_variable = json.loads(input_variable) except Exception: pass self.context['request'] = input_variable response = {v['field']: smart_jsonpath_search(input_variable, v['expression']) for v in variable_list} return NodeResult({'result': response, **response}, {}) 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, 'request': self.context.get('request'), 'result': self.context.get('result'), 'status': self.status, 'err_message': self.err_message, 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/step_node/video_understand_step_node/__init__.py ================================================ # coding=utf-8 from .impl import * ================================================ FILE: apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py ================================================ # coding=utf-8 from typing import Type from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.common import WorkflowMode from application.flow.i_step_node import INode, NodeResult class VideoUnderstandNodeSerializer(serializers.Serializer): model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Role Setting")) prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type")) is_result = serializers.BooleanField(required=False, label=_('Whether to return content')) video_list = serializers.ListField(required=False, label=_("video")) model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model parameter settings")) class IVideoUnderstandNode(INode): type = 'video-understand-node' support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: return VideoUnderstandNodeSerializer def _run(self): res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('video_list')[0], self.node_params_serializer.data.get('video_list')[1:]) if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data, **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None}) else: return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, model_params_setting, chat_record_id, video, **kwargs) -> NodeResult: pass ================================================ FILE: apps/application/flow/step_node/video_understand_step_node/impl/__init__.py ================================================ # coding=utf-8 from .base_video_understand_node import BaseVideoUnderstandNode ================================================ FILE: apps/application/flow/step_node/video_understand_step_node/impl/base_video_understand_node.py ================================================ # coding=utf-8 import time from functools import reduce from typing import List, Dict from django.db.models import QuerySet from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage from application.flow.i_step_node import NodeResult, INode from application.flow.step_node.video_understand_step_node.i_video_understand_node import IVideoUnderstandNode from knowledge.models import File from models_provider.tools import get_model_instance_by_model_workspace_id def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): chat_model = node_variable.get('chat_model') message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0 answer_tokens = chat_model.get_num_tokens(answer) node.context['message_tokens'] = message_tokens node.context['answer_tokens'] = answer_tokens node.context['answer'] = answer node.context['history_message'] = node_variable['history_message'] node.context['question'] = node_variable['question'] node.context['run_time'] = time.time() - node.context['start_time'] if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): node.answer_text = answer def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 (流式) @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = '' for chunk in response: answer += chunk.content yield chunk.content _write_context(node_variable, workflow_variable, node, workflow, answer) def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): """ 写入上下文数据 @param node_variable: 节点数据 @param workflow_variable: 全局数据 @param node: 节点实例对象 @param workflow: 工作流管理器 """ response = node_variable.get('result') answer = response.content _write_context(node_variable, workflow_variable, node, workflow, answer) def file_id_to_base64(file_id: str, video_model): file = QuerySet(File).filter(id=file_id).first() file_bytes = file.get_bytes() url = video_model.upload_file_and_get_url(file_bytes, file.file_name) return url class BaseVideoUnderstandNode(IVideoUnderstandNode): def save_context(self, details, workflow_manage): self.context['answer'] = details.get('answer') self.context['question'] = details.get('question') self.context['exception_message'] = details.get('err_message') if self.node_params.get('is_result', False): self.answer_text = details.get('answer') def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, model_params_setting, chat_record_id, video, **kwargs) -> NodeResult: workspace_id = self.workflow_manage.get_body().get('workspace_id') video_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, **model_params_setting) # 执行详情中的历史消息不需要图片内容 history_message = self.get_history_message_for_details(history_chat_record, dialogue_number) self.context['history_message'] = history_message question = self.generate_prompt_question(prompt) self.context['question'] = question.content # 生成消息列表, 真实的history_message message_list = self.generate_message_list(video_model, system, prompt, self.get_history_message(history_chat_record, dialogue_number, video_model), video) self.context['message_list'] = message_list self.generate_context_video(video) self.context['dialogue_type'] = dialogue_type if stream: r = video_model.stream(message_list) return NodeResult({'result': r, 'chat_model': video_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context_stream) else: r = video_model.invoke(message_list) return NodeResult({'result': r, 'chat_model': video_model, 'message_list': message_list, 'history_message': history_message, 'question': question.content}, {}, _write_context=write_context) def generate_context_video(self, video): if isinstance(video, str) and video.startswith('http'): self.context['video_list'] = [{'url': video}] elif video is not None and len(video) > 0: self.context['video_list'] = video def get_history_message_for_details(self, history_chat_record, dialogue_number): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message_for_details(history_chat_record[index]), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_ai_message(self, chat_record): for val in chat_record.details.values(): if self.node.id == val['node_id'] and 'video_list' in val: if val['dialogue_type'] == 'WORKFLOW': return chat_record.get_ai_message() return AIMessage(content=val.get('answer') or val.get('err_message') or '') return chat_record.get_ai_message() def generate_history_human_message_for_details(self, chat_record): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'video_list' in data: video_list = data['video_list'] # 增加对 None 和空列表的检查 if not video_list or len(video_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) file_id_list = [] url_list = [] for image in video_list: if 'file_id' in image: file_id_list.append(image.get('file_id')) elif 'url' in image: url_list.append(image.get('url')) return HumanMessage(content=[ {'type': 'text', 'text': data['question']}, *[{'type': 'video_url', 'video_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list], *[{'type': 'video_url', 'video_url': {'url': url}} for url in url_list], ]) return HumanMessage(content=chat_record.problem_text) def get_history_message(self, history_chat_record, dialogue_number, video_model): start_index = len(history_chat_record) - dialogue_number history_message = reduce(lambda x, y: [*x, *y], [ [self.generate_history_human_message(history_chat_record[index], video_model), self.generate_history_ai_message(history_chat_record[index])] for index in range(start_index if start_index > 0 else 0, len(history_chat_record))], []) return history_message def generate_history_human_message(self, chat_record, video_model): for data in chat_record.details.values(): if self.node.id == data['node_id'] and 'video_list' in data: video_list = data['video_list'] if video_list is None or len(video_list) == 0 or data['dialogue_type'] == 'WORKFLOW': return HumanMessage(content=chat_record.problem_text) file_id_list = [] url_list = [] for image in video_list: if 'file_id' in image: file_id_list.append(image.get('file_id')) elif 'url' in image: url_list.append(image.get('url')) video_base64_list = [file_id_to_base64(video.get('file_id'), video_model) for video in video_list] return HumanMessage( content=[ {'type': 'text', 'text': data['question']}, *[{'type': 'video_url', 'video_url': {'url': f'{base64_video}'}} for base64_video in video_base64_list] ]) return HumanMessage(content=chat_record.problem_text) def generate_prompt_question(self, prompt): return HumanMessage(self.workflow_manage.generate_prompt(prompt)) def _process_videos(self, image, video_model): videos = [] if isinstance(image, str) and image.startswith('http'): videos.append({'type': 'video_url', 'video_url': {'url': image}}) elif image is not None and len(image) > 0: for img in image: if 'file_id' in img: file_id = img['file_id'] file = QuerySet(File).filter(id=file_id).first() url = video_model.upload_file_and_get_url(file.get_bytes(), file.file_name) videos.append( {'type': 'video_url', 'video_url': {'url': url}}) elif 'url' in img and img['url'].startswith('http'): videos.append( {'type': 'video_url', 'video_url': {'url': img['url']}}) return videos def generate_message_list(self, video_model, system: str, prompt: str, history_message, video): prompt_text = self.workflow_manage.generate_prompt(prompt) videos = self._process_videos(video, video_model) if videos: messages = [HumanMessage(content=[{'type': 'text', 'text': prompt_text}, *videos])] else: messages = [HumanMessage(prompt_text)] if system is not None and len(system) > 0: return [ SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, *messages ] else: return [ *history_message, *messages ] @staticmethod def reset_message_list(message_list: List[BaseMessage], answer_text): result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for message in message_list] result.append({'role': 'ai', 'content': answer_text}) return result def get_details(self, index: int, **kwargs): return { 'name': self.node.properties.get('stepName'), "index": index, 'run_time': self.context.get('run_time'), 'system': self.node_params.get('system'), '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 [])], 'question': self.context.get('question'), 'answer': self.context.get('answer'), 'type': self.node.type, 'message_tokens': self.context.get('message_tokens'), 'answer_tokens': self.context.get('answer_tokens'), 'status': self.status, 'err_message': self.err_message, 'video_list': self.context.get('video_list'), 'dialogue_type': self.context.get('dialogue_type'), 'enableException': self.node.properties.get('enableException'), } ================================================ FILE: apps/application/flow/tool_loop_workflow_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: workflow_manage.py @date:2024/1/9 17:40 @desc: """ from application.flow.i_step_node import ToolFlowParamsSerializer from application.flow.loop_workflow_manage import LoopWorkflowManage class ToolLoopWorkflowManage(LoopWorkflowManage): def get_params_serializer_class(self): return ToolFlowParamsSerializer def get_source_type(self): return "TOOL" def get_source_id(self): return self.params.get('tool_id') ================================================ FILE: apps/application/flow/tool_workflow_manage.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: tool_workflow_manage.py @date:2026/3/12 15:17 @desc: """ from concurrent.futures import ThreadPoolExecutor from django.db import close_old_connections from django.utils.translation import get_language from application.flow.common import Workflow from application.flow.i_step_node import WorkFlowPostHandler, ToolFlowParamsSerializer from application.flow.workflow_manage import WorkflowManage from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse executor = ThreadPoolExecutor(max_workers=200) class ToolWorkflowManage(WorkflowManage): def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler, base_to_response: BaseToResponse = SystemToResponse(), form_data=None, start_node_id=None, start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False): super().__init__(flow, params, work_flow_post_handler, base_to_response, form_data, None, None, None, None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted) self.out_context = {} def get_params_serializer_class(self): return ToolFlowParamsSerializer def stream(self): close_old_connections() language = get_language() self.run_chain_async(self.start_node, None, language) return self.await_result(is_cleanup=False) def get_start_node(self): return self.flow.get_node('tool-start-node') def get_base_node(self): """ 获取基础节点 @return: """ return self.flow.get_node('tool-base-node') def get_source_type(self): return "TOOL" def get_source_id(self): return self.params.get('tool_id') ================================================ FILE: apps/application/flow/tools.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: utils.py @date:2024/6/6 15:15 @desc: """ from tools.models import ToolRecord, Tool from maxkb.const import CONFIG from knowledge.models.knowledge_action import State from knowledge.models import File from common.utils.logger import maxkb_logger from common.result import result from application.flow.i_step_node import WorkFlowPostHandler from application.flow.backend.sandbox_shell import SandboxShellBackend import asyncio import io import json import os import queue import re import shutil import threading import zipfile from functools import reduce from typing import Iterator import uuid_utils.compat as uuid from asgiref.sync import sync_to_async from deepagents import create_deep_agent from django.db.models import QuerySet from django.http import StreamingHttpResponse from langchain_core.messages import BaseMessageChunk, BaseMessage, ToolMessage, AIMessageChunk from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.checkpoint.memory import MemorySaver # --------------------------------------------------------------------------- # Fix: qwen's OpenAI-compatible streaming sends id='' (empty string) for # intermediate tool_call_chunks while only the first chunk carries the real # id ('call_xxx...'). langchain-core's merge_lists treats '' != 'call_xxx' as # an ID conflict and _appends_ instead of merging → the accumulated AIMessage # ends up with two separate tool_calls (one with empty args, one with empty # id) instead of one correct entry. This causes the Qwen API to reject the # next request with "function.arguments must be in JSON format". # # Patch: normalise id='' → None for items that have an 'index' key # (i.e. tool_call_chunk dicts). merge_lists treats None as "no id" and will # merge with any existing entry, keeping the real id from the first chunk. # --------------------------------------------------------------------------- import langchain_core.messages.ai as _lc_ai_module from langchain_core.utils._merge import merge_lists as _original_merge_lists def _merge_lists_normalize_empty_tool_chunk_ids(left, *others): """Wrapper around merge_lists that normalises empty-string IDs to None in tool_call_chunk items (those with an 'index' key) so that qwen streaming chunks with id='' are merged correctly by index.""" def _norm(lst): if lst is None: return lst result = [] for item in lst: if isinstance(item, dict) and 'index' in item and item.get('id') == '': item = {**item, 'id': None} result.append(item) return result return _original_merge_lists( _norm(left), *[_norm(o) for o in others], ) # Replace the module-level reference used by add_ai_message_chunks in ai.py _lc_ai_module.merge_lists = _merge_lists_normalize_empty_tool_chunk_ids class Reasoning: def __init__(self, reasoning_content_start, reasoning_content_end): self.content = "" self.reasoning_content = "" self.all_content = "" self.reasoning_content_start_tag = reasoning_content_start self.reasoning_content_end_tag = reasoning_content_end self.reasoning_content_start_tag_len = len( reasoning_content_start) if reasoning_content_start is not None else 0 self.reasoning_content_end_tag_len = len( reasoning_content_end) if reasoning_content_end is not None else 0 self.reasoning_content_end_tag_prefix = reasoning_content_end[ 0] if self.reasoning_content_end_tag_len > 0 else '' self.reasoning_content_is_start = False self.reasoning_content_is_end = False self.reasoning_content_chunk = "" def get_end_reasoning_content(self): if not self.reasoning_content_is_start and not self.reasoning_content_is_end: r = {'content': self.all_content, 'reasoning_content': ''} self.reasoning_content_chunk = "" return r if self.reasoning_content_is_start and not self.reasoning_content_is_end: r = {'content': '', 'reasoning_content': self.reasoning_content_chunk} self.reasoning_content_chunk = "" return r return {'content': '', 'reasoning_content': ''} def _normalize_content(self, content): """将不同类型的内容统一转换为字符串""" if isinstance(content, str): return content elif isinstance(content, list): # 处理包含多种内容类型的列表 normalized_parts = [] for item in content: if isinstance(item, dict): if item.get('type') == 'text': normalized_parts.append(item.get('text', '')) return ''.join(normalized_parts) else: return str(content) def get_reasoning_content(self, chunk): # 如果没有开始思考过程标签那么就全是结果 if self.reasoning_content_start_tag is None or len(self.reasoning_content_start_tag) == 0: self.content += chunk.content return {'content': chunk.content, 'reasoning_content': ''} # 如果没有结束思考过程标签那么就全部是思考过程 if self.reasoning_content_end_tag is None or len(self.reasoning_content_end_tag) == 0: return {'content': '', 'reasoning_content': chunk.content} chunk.content = self._normalize_content(chunk.content) self.all_content += chunk.content if not self.reasoning_content_is_start and len(self.all_content) >= self.reasoning_content_start_tag_len: if self.all_content.startswith(self.reasoning_content_start_tag): self.reasoning_content_is_start = True self.reasoning_content_chunk = self.all_content[self.reasoning_content_start_tag_len:] else: if not self.reasoning_content_is_end: self.reasoning_content_is_end = True self.content += self.all_content return {'content': self.all_content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content', '') if chunk.additional_kwargs else '' } else: if self.reasoning_content_is_start: self.reasoning_content_chunk += chunk.content reasoning_content_end_tag_prefix_index = self.reasoning_content_chunk.find( self.reasoning_content_end_tag_prefix) if self.reasoning_content_is_end: self.content += chunk.content return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content', '') if chunk.additional_kwargs else '' } # 是否包含结束 if reasoning_content_end_tag_prefix_index > -1: if len(self.reasoning_content_chunk) - reasoning_content_end_tag_prefix_index >= self.reasoning_content_end_tag_len: reasoning_content_end_tag_index = self.reasoning_content_chunk.find( self.reasoning_content_end_tag) if reasoning_content_end_tag_index > -1: reasoning_content_chunk = self.reasoning_content_chunk[ 0:reasoning_content_end_tag_index] content_chunk = self.reasoning_content_chunk[ reasoning_content_end_tag_index + self.reasoning_content_end_tag_len:] self.reasoning_content += reasoning_content_chunk self.content += content_chunk self.reasoning_content_chunk = "" self.reasoning_content_is_end = True return {'content': content_chunk, 'reasoning_content': reasoning_content_chunk} else: reasoning_content_chunk = self.reasoning_content_chunk[ 0:reasoning_content_end_tag_prefix_index + 1] self.reasoning_content_chunk = self.reasoning_content_chunk.replace( reasoning_content_chunk, '') self.reasoning_content += reasoning_content_chunk return {'content': '', 'reasoning_content': reasoning_content_chunk} else: return {'content': '', 'reasoning_content': ''} else: if self.reasoning_content_is_end: self.content += chunk.content return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content', '') if chunk.additional_kwargs else '' } else: # aaa result = {'content': '', 'reasoning_content': self.reasoning_content_chunk} self.reasoning_content += self.reasoning_content_chunk self.reasoning_content_chunk = "" return result def event_content(chat_id, chat_record_id, response, workflow, write_context, post_handler: WorkFlowPostHandler): """ 用于处理流式输出 @param chat_id: 会话id @param chat_record_id: 对话记录id @param response: 响应数据 @param workflow: 工作流管理器 @param write_context 写入节点上下文 @param post_handler: 后置处理器 """ answer = '' try: for chunk in response: answer += chunk.content yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': chunk.content, 'is_end': False}, ensure_ascii=False) + "\n\n" write_context(answer, 200) post_handler.handler(chat_id, chat_record_id, answer, workflow) yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': '', 'is_end': True}, ensure_ascii=False) + "\n\n" except Exception as e: answer = str(e) write_context(answer, 500) post_handler.handler(chat_id, chat_record_id, answer, workflow) yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': answer, 'is_end': True}, ensure_ascii=False) + "\n\n" def to_stream_response(chat_id, chat_record_id, response: Iterator[BaseMessageChunk], workflow, write_context, post_handler): """ 将结果转换为服务流输出 @param chat_id: 会话id @param chat_record_id: 对话记录id @param response: 响应数据 @param workflow: 工作流管理器 @param write_context 写入节点上下文 @param post_handler: 后置处理器 @return: 响应 """ r = StreamingHttpResponse( streaming_content=event_content( chat_id, chat_record_id, response, workflow, write_context, post_handler), content_type='text/event-stream;charset=utf-8', charset='utf-8') r['Cache-Control'] = 'no-cache' return r def to_response(chat_id, chat_record_id, response: BaseMessage, workflow, write_context, post_handler: WorkFlowPostHandler): """ 将结果转换为服务输出 @param chat_id: 会话id @param chat_record_id: 对话记录id @param response: 响应数据 @param workflow: 工作流管理器 @param write_context 写入节点上下文 @param post_handler: 后置处理器 @return: 响应 """ answer = response.content write_context(answer) post_handler.handler(chat_id, chat_record_id, answer, workflow) return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': answer, 'is_end': True}) def to_response_simple(chat_id, chat_record_id, response: BaseMessage, workflow, post_handler: WorkFlowPostHandler): answer = response.content post_handler.handler(chat_id, chat_record_id, answer, workflow) return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': answer, 'is_end': True}) def to_stream_response_simple(stream_event): r = StreamingHttpResponse( streaming_content=stream_event, content_type='text/event-stream;charset=utf-8', charset='utf-8') r['Cache-Control'] = 'no-cache' return r def generate_tool_message_complete(icon, name, input_content, output_content): """生成包含输入和输出的工具消息模版""" # 确保输入内容是字符串,如果不是则尝试转换为 JSON 字符串 if not isinstance(input_content, str): input_content = json.dumps(input_content, ensure_ascii=False) # 格式化输出 if not isinstance(output_content, str): output_content = json.dumps(output_content, ensure_ascii=False) content = { "icon": icon, "title": name, "type": "simple-tool-calls", "content": { "input": input_content, "output": output_content } } return f'{json.dumps(content)}' # 全局单例事件循环 _global_loop = None _loop_thread = None _loop_lock = threading.Lock() def get_global_loop(): """获取全局共享的事件循环""" global _global_loop, _loop_thread with _loop_lock: if _global_loop is None: _global_loop = asyncio.new_event_loop() def run_forever(): asyncio.set_event_loop(_global_loop) _global_loop.run_forever() _loop_thread = threading.Thread( target=run_forever, daemon=True, name="GlobalAsyncLoop") _loop_thread.start() return _global_loop def _extract_tool_id(raw_id): """从 raw_id 中提取最后一个符合 call_... 模式的 id,若无匹配则返回原值或 None""" if not raw_id: return None if not isinstance(raw_id, str): raw_id = str(raw_id) s = raw_id prefix = 'call_' positions = [m.start() for m in re.finditer(re.escape(prefix), s)] if not positions: return raw_id # 取最后一个前缀位置,截到下一个前缀或结尾 start = positions[-1] end = len(s) for pos in positions: if pos > start: end = pos break tool_id = s[start:end] return tool_id or raw_id async def _initialize_skills(mcp_servers, temp_dir): skills_dir = os.path.join(temp_dir, 'skills') mcp_config = json.loads(mcp_servers) if "skills" in mcp_config: skill_file_items = mcp_config.pop('skills') for skill_file in skill_file_items: # 使用 sync_to_async 包装 ORM 查询 file = await sync_to_async(lambda: QuerySet(File).filter(id=skill_file['file_id']).first())() if not file: continue # get_bytes 可能也涉及 IO,也用 sync_to_async 包装 file_bytes = await sync_to_async(file.get_bytes)() params = skill_file.get('params', {}) with zipfile.ZipFile(io.BytesIO(file_bytes), 'r') as zip_ref: members = [ m for m in zip_ref.namelist() if not m.startswith('__MACOSX/') and '__MACOSX' not in m ] for member in members: if ".." in member or member.startswith("/"): raise ValueError(f"非法路径: {member}") zip_ref.extractall(skills_dir, members=members) # 获取技能解压后的顶级目录名 top_level_dirs = set() for member in members: parts = member.split('/') if parts[0]: top_level_dirs.add(parts[0]) # 将 params 写入每个顶级目录下的 .env 文件 if params: env_lines = [] for key, value in params.items(): # 对含空格或特殊字符的值加引号 env_lines.append(f'{key}={value}') env_content = '\n'.join(env_lines) + '\n' for top_dir in top_level_dirs: env_path = os.path.join(skills_dir, top_dir, '.env') with open(env_path, 'w', encoding='utf-8') as f: f.write(env_content) os.system("chmod -R g+rx " + temp_dir) # 确保技能目录可访问 client = MultiServerMCPClient(mcp_config) return client async def _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_enable=True, tool_init_params={}, source_id=None, source_type=None, temp_dir=None, chat_id=None): try: checkpointer = MemorySaver() client = await _initialize_skills(mcp_servers, temp_dir) tools = await client.get_tools() agent = create_deep_agent( model=chat_model, backend=SandboxShellBackend(root_dir=temp_dir, virtual_mode=True), skills=['/skills'], tools=tools, interrupt_on={ "write_file": False, "read_file": False, "edit_file": False }, checkpointer=checkpointer, ) recursion_limit = int(CONFIG.get( "LANGCHAIN_GRAPH_RECURSION_LIMIT", '100')) response = agent.astream( {"messages": message_list}, config={"recursion_limit": recursion_limit, "configurable": {"thread_id": chat_id}}, stream_mode='messages' ) tool_calls_info = {} # tool_id -> {'name': ..., 'input': ...} # key(index/id) -> {'id': ..., 'name': ..., 'arguments': ...} _tool_fragments = {} def _merge_arguments(entry, part_args): if not isinstance(part_args, str): try: part_args = json.dumps(part_args, ensure_ascii=False) except Exception: part_args = str(part_args) if part_args else '' if not part_args: return # Some providers first emit placeholder args like "{}" and then # stream the real JSON fragments via later chunks. Prefer fragments. if entry['arguments'] in ('{}', '[]') and part_args.startswith('{'): entry['arguments'] = part_args return if entry['arguments']: try: existing_obj = json.loads(entry['arguments']) new_obj = json.loads(part_args) if isinstance(existing_obj, dict) and isinstance(new_obj, dict): merged = {**existing_obj, **new_obj} entry['arguments'] = json.dumps( merged, ensure_ascii=False) else: entry['arguments'] += part_args except (json.JSONDecodeError, ValueError): entry['arguments'] += part_args else: entry['arguments'] = part_args def _get_fragment_key(idx, raw_id): if idx is not None: return f'idx:{idx}' if raw_id and str(raw_id).strip(): return f"id:{_extract_tool_id(str(raw_id).strip())}" return None def _upsert_fragment(key, raw_id, func_name, part_args): if key is None: return entry = _tool_fragments.setdefault( key, {'id': '', 'name': '', 'arguments': ''}) if raw_id and str(raw_id).strip(): new_id = str(raw_id).strip() if entry.get('completed') and entry.get('id') and entry['id'] != new_id: maxkb_logger.debug( f"Resetting completed fragment {key}: old ID {entry['id']} -> new ID {new_id}") entry.clear() entry.update({'id': '', 'name': '', 'arguments': ''}) entry['id'] = new_id if func_name: entry['name'] = func_name _merge_arguments(entry, part_args) async for chunk in response: # print(chunk) if isinstance(chunk[0], AIMessageChunk): # ---------------------------------------------------------------- # 1. 从 tool_call_chunks 中聚合工具调用片段 # (qwen/OpenAI streaming 通过 tool_call_chunks 传递, # additional_kwargs['tool_calls'] 在流式时通常为空) # ---------------------------------------------------------------- for tc_chunk in (chunk[0].tool_call_chunks or []): raw_id = tc_chunk.get('id') key = _get_fragment_key(tc_chunk.get('index'), raw_id) _upsert_fragment( key, raw_id, tc_chunk.get('name'), tc_chunk.get('args', '') ) # ---------------------------------------------------------------- # 1.1 兼容部分模型将工具调用放在 chunk.tool_calls,且 tool_call_chunks # 的 index 为空(例如 ollama/qwen) # ---------------------------------------------------------------- has_tool_call_chunks = bool(chunk[0].tool_call_chunks) for tool_call in (chunk[0].tool_calls or []): raw_id = tool_call.get('id') part_args = tool_call.get('args', '') # qwen-plus often emits {} here as a placeholder while # the real args are split in tool_call_chunks/invalid_tool_calls. if has_tool_call_chunks and ( part_args == '' or part_args == {} or part_args == [] ): part_args = '' key = _get_fragment_key(tool_call.get('index'), raw_id) _upsert_fragment( key, raw_id, tool_call.get('name'), part_args ) # ---------------------------------------------------------------- # 1.2 兼容 invalid_tool_calls 分片(部分模型会把中间 JSON 片段放这里) # ---------------------------------------------------------------- for invalid_tool_call in (chunk[0].invalid_tool_calls or []): raw_id = invalid_tool_call.get('id') key = _get_fragment_key( invalid_tool_call.get('index'), raw_id) _upsert_fragment( key, raw_id, invalid_tool_call.get('name'), invalid_tool_call.get('args', '') ) # ---------------------------------------------------------------- # 2. 兼容 additional_kwargs['tool_calls'] 方式(旧格式/非流式情况) # ---------------------------------------------------------------- legacy_tool_calls = chunk[0].additional_kwargs.get( 'tool_calls', []) for tool_call in legacy_tool_calls: raw_id = tool_call.get('id') func = tool_call.get('function', {}) if isinstance(func, dict): func_name = func.get('name') part_args = func.get('arguments', '') else: func_name = tool_call.get('name') part_args = tool_call.get('arguments', '') key = _get_fragment_key(tool_call.get('index'), raw_id) _upsert_fragment(key, raw_id, func_name, part_args) # ---------------------------------------------------------------- # 3. 检测工具调用结束,更新 tool_calls_info # ---------------------------------------------------------------- is_finish_chunk = ( chunk[0].response_metadata.get( 'finish_reason') == 'tool_calls' or chunk[0].chunk_position == 'last' ) if is_finish_chunk: # 在 finish chunk 时,将所有未完成的 fragment 标记完成并更新 tool_calls_info maxkb_logger.debug( f"Processing finish chunk. Tool fragments: {_tool_fragments}") for idx, entry in _tool_fragments.items(): if entry.get('completed'): maxkb_logger.debug( f"Skipping fragment {idx}: already completed") continue if not entry.get('id'): maxkb_logger.debug( f"Skipping fragment {idx}: missing id. Fragment: {entry}") continue if not entry.get('arguments'): maxkb_logger.debug( f"Skipping fragment {idx}: missing arguments. Fragment: {entry}") continue if not entry.get('completed') and entry.get('id') and entry.get('arguments'): try: parsed_args = json.loads(entry['arguments']) filtered_args = { k: v for k, v in parsed_args.items() if k not in tool_init_params } if tool_init_params else parsed_args normalized_id = _extract_tool_id(entry['id']) info = { 'name': entry['name'], 'input': json.dumps(filtered_args, ensure_ascii=False) } tool_calls_info[entry['id']] = info if normalized_id and normalized_id != entry['id']: tool_calls_info[normalized_id] = info entry['completed'] = True maxkb_logger.debug( f"Added tool call {entry['id']} to tool_calls_info") except (json.JSONDecodeError, ValueError) as e: # JSON parsing failed, but still add to tool_calls_info with raw arguments # to prevent "Tool ID not found" errors when ToolMessage arrives maxkb_logger.warning( f"Failed to parse tool arguments at finish for tool {entry.get('id', 'unknown')}: " f"{entry['arguments']}, error: {e}. Using raw arguments.") normalized_id = _extract_tool_id(entry['id']) info = { 'name': entry['name'], # Use raw arguments 'input': entry['arguments'] } tool_calls_info[entry['id']] = info if normalized_id and normalized_id != entry['id']: tool_calls_info[normalized_id] = info entry['completed'] = True # ---------------------------------------------------------------- # 4. 修复 tool_call_chunks 中的空 id(回填已知 id) # ---------------------------------------------------------------- if chunk[0].tool_call_chunks: for tc_chunk in chunk[0].tool_call_chunks: key = _get_fragment_key( tc_chunk.get('index'), tc_chunk.get('id')) if key is not None: frag = _tool_fragments.get(key) if frag and frag.get('id') and not tc_chunk.get('id'): tc_chunk['id'] = frag['id'] # ---------------------------------------------------------------- # 5. 修复 additional_kwargs['tool_calls'](兼容旧格式) # 仅在 finish chunk 时写入完整参数,避免污染中间 chunk 的 # additional_kwargs(中间 chunk 会被 ainvoke 累积,如果写入 # 不完整 JSON 会导致下一轮 API 调用出现 arguments 非 JSON 格式错误) # ---------------------------------------------------------------- if legacy_tool_calls and is_finish_chunk: fixed_tool_calls = [] for tool_call in legacy_tool_calls: key = _get_fragment_key( tool_call.get('index'), tool_call.get('id')) frag = _tool_fragments.get( key) if key is not None else None tc = dict(tool_call) if frag and frag.get('id') and not tc.get('id'): tc['id'] = frag['id'] if frag and isinstance(tc.get('function'), dict): tc['function'] = dict(tc['function']) if frag.get('completed'): tc['function']['arguments'] = frag['arguments'] fixed_tool_calls.append(tc) chunk[0].additional_kwargs['tool_calls'] = fixed_tool_calls yield chunk[0] if mcp_output_enable and isinstance(chunk[0], ToolMessage): tool_id = chunk[0].tool_call_id normalized_tool_id = _extract_tool_id(tool_id) tool_info = tool_calls_info.get(tool_id) or tool_calls_info.get( normalized_tool_id) if tool_info: try: if isinstance(chunk[0].content, str): tool_result = json.loads(chunk[0].content) elif isinstance(chunk[0].content, dict): tool_result = chunk[0].content elif isinstance(chunk[0].content, list): tool_result = chunk[0].content[0] if len( chunk[0].content) > 0 else {} else: tool_result = {} text = tool_result.get('text') if 'text' in tool_result else None text_result = json.loads(text) if text else tool_result if text: tool_lib_id = text_result.pop('tool_id') if 'tool_id' in text_result else None else: tool_lib_id = tool_result.pop('tool_id') if 'tool_id' in tool_result else None if tool_lib_id: await save_tool_record(tool_lib_id, tool_info, tool_result, source_id, source_type) tool_result = json.dumps(text_result, ensure_ascii=False) except Exception as e: tool_result = chunk[0].content content = generate_tool_message_complete( tool_info.get('icon', ''), tool_info['name'], tool_info['input'], tool_result ) chunk[0].content = content else: maxkb_logger.warning( f"Tool ID {tool_id} not found in tool_calls_info. " f"Normalized Tool ID: {normalized_tool_id}. " f"Available IDs: {list(tool_calls_info.keys())}. " f"Tool fragments at this point: {_tool_fragments}" ) yield chunk[0] except ExceptionGroup as eg: def get_real_error(exc): if isinstance(exc, ExceptionGroup): return get_real_error(exc.exceptions[0]) return exc real_error = get_real_error(eg) error_msg = f"{type(real_error).__name__}: {str(real_error)}" raise RuntimeError(error_msg) from None except Exception as e: error_msg = f"{type(e).__name__}: {str(e)}" raise RuntimeError(error_msg) from None async def save_tool_record(tool_id, tool_info, tool_result, source_id, source_type): tool = await sync_to_async(lambda: QuerySet(Tool).filter(id=tool_id).first())() tool_info['icon'] = tool.icon tool_record = ToolRecord( id=uuid.uuid7(), workspace_id=tool.workspace_id, tool_id=tool_id, source_type=source_type, source_id=source_id, meta={'input': tool_info['input'], 'output': tool_result}, state=State.SUCCESS ) await sync_to_async(tool_record.save)() def mcp_response_generator(chat_model, message_list, mcp_servers, mcp_output_enable=True, tool_init_params={}, source_id=None, source_type=None, chat_id=None): """使用全局事件循环,不创建新实例""" result_queue = queue.Queue() loop = get_global_loop() # 使用共享循环 # 创建临时文件夹 if chat_id: temp_dir = os.path.join('/tmp', chat_id[:8]) else: temp_dir = os.path.join('/tmp', uuid.uuid7().hex[:8]) skills_dir = os.path.join(temp_dir, 'skills') os.makedirs(skills_dir, exist_ok=True) # print(f"Initializing skills in temporary directory: {skills_dir}") async def _run(): try: async_gen = _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_enable, tool_init_params, source_id, source_type, temp_dir, chat_id) async for chunk in async_gen: result_queue.put(('data', chunk)) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) result_queue.put(('error', e)) finally: result_queue.put(('done', None)) # 在全局循环中调度任务 asyncio.run_coroutine_threadsafe(_run(), loop) while True: msg_type, data = result_queue.get() if msg_type == 'done': # 清理临时文件夹 shutil.rmtree(temp_dir, ignore_errors=True) break if msg_type == 'error': # 清理临时文件夹 shutil.rmtree(temp_dir, ignore_errors=True) raise data yield data async def anext_async(agen): return await agen.__anext__() target_source_node_mapping = { 'TOOL': {'tool-lib-node': lambda n: [n.get('properties').get('node_data').get('tool_lib_id')], 'ai-chat-node': lambda n: [*(n.get('properties').get('node_data').get('mcp_tool_ids') or []), *(n.get('properties').get('node_data').get('tool_ids') or []), *(n.get('properties').get('node_data').get('skill_tool_ids') or [])], 'mcp-node': lambda n: [n.get('properties').get('node_data').get('mcp_tool_id')] }, 'MODEL': {'ai-chat-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'question-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'speech-to-text-node': lambda n: [n.get('properties').get('node_data').get('stt_model_id')], 'text-to-speech-node': lambda n: [n.get('properties').get('node_data').get('tts_model_id')], 'image-to-video-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'image-generate-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'intent-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'image-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'parameter-extraction-node': lambda n: [n.get('properties').get('node_data').get('model_id')], 'video-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')], }, 'KNOWLEDGE': {'search-knowledge-node': lambda n: n.get('properties').get('node_data').get('knowledge_id_list')}, 'APPLICATION': { 'application-node': lambda n: [n.get('properties').get('node_data').get('application_id')] } } def get_node_handle_callback(source_type, source_id): def node_handle_callback(node): from system_manage.models.resource_mapping import ResourceMapping response = [] for key, value in target_source_node_mapping.items(): if node.get('type') in value: call = value.get(node.get('type')) target_source_id_list = call(node) for target_source_id in target_source_id_list: if target_source_id: response.append(ResourceMapping(source_type=source_type, target_type=key, source_id=source_id, target_id=target_source_id)) return response return node_handle_callback def get_workflow_resource(workflow, node_handle): response = [] if 'nodes' in workflow: for node in workflow.get('nodes'): rs = node_handle(node) if rs: for r in rs: response.append(r) if node.get('type') == 'loop-node': r = get_workflow_resource(node.get('properties', {}).get( 'node_data', {}).get('loop_body'), node_handle) for rn in r: response.append(rn) return list({(str(item.target_type) + str(item.target_id)): item for item in response}.values()) return [] application_instance_field_call_dict = { 'TOOL': [ lambda instance: instance.mcp_tool_ids or [], lambda instance: instance.skill_tool_ids or [], lambda instance: instance.tool_ids or [] ], 'MODEL': [ lambda instance: [instance.model_id] if instance.model_id else [], lambda instance: [ instance.tts_model_id] if instance.tts_model_id else [], lambda instance: [ instance.stt_model_id] if instance.stt_model_id else [] ] } knowledge_instance_field_call_dict = { 'MODEL': [lambda instance: [instance.embedding_model_id] if instance.embedding_model_id else []], } def get_instance_resource(instance, source_type, source_id, instance_field_call_dict): response = [] from system_manage.models.resource_mapping import ResourceMapping for target_type, call_list in instance_field_call_dict.items(): target_id_list = reduce( lambda x, y: [*x, *y], [call(instance) for call in call_list], []) if target_id_list: for target_id in target_id_list: response.append(ResourceMapping(source_type=source_type, target_type=target_type, source_id=source_id, target_id=target_id)) return response def save_workflow_mapping(workflow, source_type, source_id, other_resource_mapping=None): if not other_resource_mapping: other_resource_mapping = [] from system_manage.models.resource_mapping import ResourceMapping from django.db.models import QuerySet QuerySet(ResourceMapping).filter( source_type=source_type, source_id=source_id).delete() resource_mapping_list = get_workflow_resource(workflow, get_node_handle_callback(source_type, source_id)) resource_mapping_list += other_resource_mapping if resource_mapping_list: QuerySet(ResourceMapping).bulk_create( {(str(item.target_type) + str(item.target_id)): item for item in resource_mapping_list}.values()) def get_tool_id_list(workflow): _result = [] for node in workflow.get('nodes', []): if node.get('type') == 'tool-lib-node': tool_id = node.get('properties', {}).get( 'node_data', {}).get('tool_lib_id') if tool_id: _result.append(tool_id) elif node.get('type') == 'loop-node': r = get_tool_id_list(node.get('properties', {}).get( 'node_data', {}).get('loop_body', {})) for item in r: _result.append(item) elif node.get('type') == 'ai-chat-node': node_data = node.get('properties', {}).get('node_data', {}) mcp_tool_ids = node_data.get('mcp_tool_ids') or [] skill_tool_ids = node_data.get('skill_tool_ids') or [] tool_ids = node_data.get('tool_ids') or [] for _id in mcp_tool_ids + tool_ids + skill_tool_ids: _result.append(_id) elif node.get('type') == 'mcp-node': mcp_tool_id = node.get('properties', {}).get( 'node_data', {}).get('mcp_tool_id') if mcp_tool_id: _result.append(mcp_tool_id) return _result ================================================ FILE: apps/application/flow/workflow_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: workflow_manage.py @date:2024/1/9 17:40 @desc: """ import concurrent import json import threading from concurrent.futures import ThreadPoolExecutor from functools import reduce from typing import List, Dict from django.db import close_old_connections, connection from django.utils import translation from django.utils.translation import get_language from langchain_core.prompts import PromptTemplate from rest_framework import status from application.flow import tools from application.flow.common import Workflow from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult, FlowParamsSerializer from application.flow.step_node import get_node from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse from common.utils.logger import maxkb_logger executor = ThreadPoolExecutor(max_workers=200) class NodeResultFuture: def __init__(self, r, e, status=200): self.r = r self.e = e self.status = status def result(self): if self.status == 200: return self.r else: raise self.e def await_result(result, timeout=1): try: result.result(timeout) return False except Exception as e: return True class NodeChunkManage: def __init__(self, work_flow): self.node_chunk_list = [] self.current_node_chunk = None self.work_flow = work_flow def add_node_chunk(self, node_chunk): self.node_chunk_list.append(node_chunk) def contains(self, node_chunk): return self.node_chunk_list.__contains__(node_chunk) def pop(self): if self.current_node_chunk is None: try: current_node_chunk = self.node_chunk_list.pop(0) self.current_node_chunk = current_node_chunk except IndexError as e: pass if self.current_node_chunk is not None: try: chunk = self.current_node_chunk.chunk_list.pop(0) return chunk except IndexError as e: if self.current_node_chunk.is_end(): self.current_node_chunk = None if self.work_flow.answer_is_not_empty(): chunk = self.work_flow.base_to_response.to_stream_chunk_response( self.work_flow.params['chat_id'], self.work_flow.params['chat_record_id'], '\n\n', False, 0, 0) self.work_flow.append_answer('\n\n') return chunk return self.pop() return None class WorkflowManage: def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler, base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None, document_list=None, audio_list=None, video_list=None, other_list=None, start_node_id=None, start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False): if form_data is None: form_data = {} if image_list is None: image_list = [] if document_list is None: document_list = [] if audio_list is None: audio_list = [] if video_list is None: video_list = [] if other_list is None: other_list = [] self.start_node_id = start_node_id self.start_node = None self.form_data = form_data self.image_list = image_list self.video_list = video_list self.document_list = document_list self.audio_list = audio_list self.other_list = other_list self.params = params self.flow = flow self.context = {} self.chat_context = {} self.node_chunk_manage = NodeChunkManage(self) self.work_flow_post_handler = work_flow_post_handler self.current_node = None self.current_result = None self.answer = "" self.answer_list = [''] self.status = 200 self.base_to_response = base_to_response self.chat_record = chat_record self.child_node = child_node self.future_list = [] self.lock = threading.Lock() self.field_list = [] self.global_field_list = [] self.chat_field_list = [] self.init_fields() self.is_the_task_interrupted = is_the_task_interrupted if start_node_id is not None: self.load_node(chat_record, start_node_id, start_node_data) else: self.node_context = [] def init_fields(self): field_list = [] global_field_list = [] chat_field_list = [] for node in self.flow.nodes: properties = node.properties node_name = properties.get('stepName') node_id = node.id node_config = properties.get('config') field_list.append( {'label': '异常信息', 'value': 'exception_message', 'node_id': node_id, 'node_name': node_name}) if node_config is not None: fields = node_config.get('fields') if fields is not None: for field in fields: field_list.append({**field, 'node_id': node_id, 'node_name': node_name}) global_fields = node_config.get('globalFields') 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}) chat_fields = node_config.get('chatFields') if chat_fields is not None: for chat_field in chat_fields: chat_field_list.append({**chat_field, 'node_id': node_id, 'node_name': node_name}) 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) chat_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 self.chat_field_list = chat_field_list def append_answer(self, content): self.answer += content self.answer_list[-1] += content def answer_is_not_empty(self): return len(self.answer_list[-1]) > 0 def load_node(self, chat_record, start_node_id, start_node_data): self.node_context = [] self.answer = chat_record.answer_text self.answer_list = chat_record.answer_text_list self.answer_list.append('') for node_details in sorted(chat_record.details.values(), key=lambda d: d.get('index')): node_id = node_details.get('node_id') if node_details.get('runtime_node_id') == start_node_id: def get_node_params(n): is_result = False if ['application-node', 'loop-node', 'tool-workflow-lib-node'].__contains__(n.type): is_result = True return {**n.properties.get('node_data'), 'form_data': start_node_data, 'node_data': start_node_data, 'child_node': self.child_node, 'is_result': is_result} self.start_node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list'), get_node_params=get_node_params) self.start_node.valid_args( {**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params) if self.start_node.type == 'loop-node': loop_node_data = node_details.get('loop_node_data', {}) for k, v in node_details.get('loop_context_data').items(): if v is not None: self.start_node.context[k] = v self.start_node.context['loop_node_data'] = loop_node_data self.start_node.context['current_index'] = node_details.get('current_index') self.start_node.context['current_item'] = node_details.get('current_item') self.start_node.context['loop_answer_data'] = node_details.get('loop_answer_data', {}) if self.start_node.type == 'application-node': application_node_dict = node_details.get('application_node_dict', {}) self.start_node.context['application_node_dict'] = application_node_dict self.node_context.append(self.start_node) continue node_id = node_details.get('node_id') node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list')) node.valid_args(node.node_params, node.workflow_params) node.save_context(node_details, self) node.node_chunk.end() self.node_context.append(node) def run(self): close_old_connections() language = get_language() if self.params.get('stream'): return self.run_stream(self.start_node, None, language) return self.run_block(language) def run_block(self, language='zh'): """ 非流式响应 @return: 结果 """ try: self.run_chain_async(None, None, language) while self.is_run(): pass details = self.get_runtime_details() message_tokens = sum([row.get('message_tokens') for row in details.values() if 'message_tokens' in row and row.get('message_tokens') is not None]) answer_tokens = sum([row.get('answer_tokens') for row in details.values() if 'answer_tokens' in row and row.get('answer_tokens') is not None]) answer_text_list = self.get_answer_text_list() answer_text = '\n\n'.join( '\n\n'.join([a.get('content') for a in answer]) for answer in answer_text_list) answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, []) self.work_flow_post_handler.handler(self) res = self.base_to_response.to_block_response(self.params['chat_id'], self.params['chat_record_id'], answer_text, True , message_tokens, answer_tokens, _status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR, other_params={'answer_list': answer_list}) finally: self._cleanup() return res def _cleanup(self): """清理所有对象引用""" # 清理列表 self.future_list.clear() self.field_list.clear() self.global_field_list.clear() self.chat_field_list.clear() self.image_list.clear() self.video_list.clear() self.document_list.clear() self.audio_list.clear() self.other_list.clear() if hasattr(self, 'node_context'): self.node_context.clear() # 清理字典 self.context.clear() self.chat_context.clear() self.form_data.clear() # 清理对象引用 self.node_chunk_manage = None self.work_flow_post_handler = None self.flow = None self.start_node = None self.current_node = None self.current_result = None self.chat_record = None self.base_to_response = None self.params = None self.lock = None def run_stream(self, current_node, node_result_future, language='zh'): """ 流式响应 @return: """ self.run_chain_async(current_node, node_result_future, language) return tools.to_stream_response_simple(self.await_result()) def get_body(self): return self.params def is_run(self, timeout=0.5): future_list_len = len(self.future_list) try: r = concurrent.futures.wait(self.future_list, timeout) if len(r.not_done) > 0: return True else: if future_list_len == len(self.future_list): return False else: return True except Exception as e: return True def await_result(self, is_cleanup=True): try: while self.is_run(): while True: chunk = self.node_chunk_manage.pop() if chunk is not None: yield chunk else: break while True: chunk = self.node_chunk_manage.pop() if chunk is None: break yield chunk finally: while self.is_run(): pass details = self.get_runtime_details() message_tokens = sum([row.get('message_tokens') for row in details.values() if 'message_tokens' in row and row.get('message_tokens') is not None]) answer_tokens = sum([row.get('answer_tokens') for row in details.values() if 'answer_tokens' in row and row.get('answer_tokens') is not None]) self.work_flow_post_handler.handler(self) yield self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'), self.params.get('chat_record_id'), '', [], '', True, message_tokens, answer_tokens, {}) if is_cleanup: self._cleanup() def run_chain_async(self, current_node, node_result_future, language='zh'): future = executor.submit(self.run_chain_manage, current_node, node_result_future, language) self.future_list.append(future) def run_chain_manage(self, current_node, node_result_future, language='zh'): translation.activate(language) if current_node is None: start_node = self.get_start_node() current_node = get_node(start_node.type, self.flow.workflow_mode)(start_node, self.params, self) self.node_chunk_manage.add_node_chunk(current_node.node_chunk) # 添加节点 self.append_node(current_node) result = self.run_chain(current_node, node_result_future) if result is None: return node_list = self.get_next_node_list(current_node, result) if len(node_list) == 1: self.run_chain_manage(node_list[0], None, language) elif len(node_list) > 1: sorted_node_run_list = sorted(node_list, key=lambda n: n.node.y) # 获取到可执行的子节点 result_list = [{'node': node, 'future': executor.submit(self.run_chain_manage, node, None, language)} for node in sorted_node_run_list] for r in result_list: self.future_list.append(r.get('future')) def run_chain(self, current_node, node_result_future=None): if node_result_future is None: node_result_future = self.run_node_future(current_node) try: is_stream = self.params.get('stream', True) result = self.hand_event_node_result(current_node, node_result_future) if is_stream else self.hand_node_result( current_node, node_result_future) return result except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) return None def hand_node_result(self, current_node, node_result_future): try: current_result = node_result_future.result() result = current_result.write_context(current_node, self) if result is not None: # 阻塞获取结果 list(result) return current_result except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) self.status = 500 current_node.get_write_error_context(e) self.answer += str(e) finally: current_node.node_chunk.end() def append_node(self, current_node): for index in range(len(self.node_context)): n = self.node_context[index] if current_node.id == n.node.id and current_node.runtime_node_id == n.runtime_node_id: self.node_context[index] = current_node return self.node_context.append(current_node) def hand_event_node_result(self, current_node, node_result_future): runtime_node_id = current_node.runtime_node_id real_node_id = current_node.runtime_node_id child_node = {} view_type = current_node.view_type try: current_result = node_result_future.result() result = current_result.write_context(current_node, self) if result is not None: if self.is_result(current_node, current_result): for r in result: reasoning_content = '' content = r child_node = {} node_is_end = False view_type = current_node.view_type node_type = current_node.type if isinstance(r, dict): content = r.get('content') child_node = {'runtime_node_id': r.get('runtime_node_id'), 'chat_record_id': r.get('chat_record_id') , 'child_node': r.get('child_node')} if r.__contains__('real_node_id'): real_node_id = r.get('real_node_id') if r.__contains__('node_is_end'): node_is_end = r.get('node_is_end') if r.__contains__('node_type'): node_type = r.get("node_type") view_type = r.get('view_type') reasoning_content = r.get('reasoning_content') chunk = self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'), self.params.get('chat_record_id'), current_node.id, current_node.up_node_id_list, content, False, 0, 0, {'node_type': node_type, 'runtime_node_id': runtime_node_id, 'view_type': view_type, 'child_node': child_node, 'node_is_end': node_is_end, 'real_node_id': real_node_id, 'reasoning_content': reasoning_content, 'node_status': "SUCCESS"}) current_node.node_chunk.add_chunk(chunk) chunk = (self.base_to_response .to_stream_chunk_response(self.params.get('chat_id'), self.params.get('chat_record_id'), current_node.id, current_node.up_node_id_list, '', False, 0, 0, {'node_is_end': True, 'runtime_node_id': runtime_node_id, 'node_type': current_node.type, 'view_type': view_type, 'child_node': child_node, 'real_node_id': real_node_id, 'reasoning_content': '', 'node_status': "SUCCESS"})) current_node.node_chunk.add_chunk(chunk) else: list(result) if current_node.status == 500: enableException = current_node.node.properties.get('enableException') if not enableException: return None current_node.context['exception_message'] = current_node.err_message current_node.context['branch_id'] = 'exception' r = NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {}, _is_interrupt=lambda node, step_variable, global_variable: False) r.write_context(current_node, self) return r if self.is_the_task_interrupted(): current_node.status = 201 return None return current_result except Exception as e: # 添加节点 maxkb_logger.error(f'Exception: {e}', exc_info=True) enableException = current_node.node.properties.get('enableException') current_node.get_write_error_context(e) self.status = 500 if self.is_the_task_interrupted(): current_node.status = 201 return None if not enableException: chunk = self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'), self.params.get('chat_id'), current_node.id, current_node.up_node_id_list, 'Exception:' + str(e), False, 0, 0, {'node_is_end': True, 'runtime_node_id': current_node.runtime_node_id, 'node_type': current_node.type, 'view_type': current_node.view_type, 'child_node': {}, 'real_node_id': real_node_id, 'node_status': 'ERROR'}) current_node.node_chunk.add_chunk(chunk) return None else: current_node.context['exception_message'] = current_node.err_message current_node.context['branch_id'] = 'exception' return NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {}, _is_interrupt=lambda node, step_variable, global_variable: False) finally: current_node.node_chunk.end() # 归还链接到连接池 connection.close() def run_node_async(self, node): future = executor.submit(self.run_node, node) return future def run_node_future(self, node): try: node.valid_args(node.node_params, node.workflow_params) result = self.run_node(node) return NodeResultFuture(result, None, 200) except Exception as e: return NodeResultFuture(None, e, 500) def run_node(self, node): result = node.run() return result def is_result(self, current_node, current_node_result): return current_node.node_params.get('is_result', not self._has_next_node( current_node, current_node_result)) if current_node.node_params is not None else False def get_chat_info(self): return self.work_flow_post_handler.chat_info def get_chunk_content(self, chunk, is_end=False): return 'data: ' + json.dumps( {'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True, 'content': chunk, 'is_end': is_end}, ensure_ascii=False) + "\n\n" def _has_next_node(self, current_node, node_result: NodeResult | None): """ 是否有下一个可运行的节点 """ next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or [] for next_edge_node in next_edge_node_list: if node_result is not None and node_result.is_assertion_result(): edge = next_edge_node.edge if (edge.sourceNodeId == current_node.id and f"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId): return True return len(next_edge_node_list) > 0 def has_next_node(self, node_result: NodeResult | None): """ 是否有下一个可运行的节点 """ return self._has_next_node(self.get_start_node() if self.current_node is None else self.current_node, node_result) def get_runtime_details(self, get_details=lambda n, index: n.get_details(index)): details_result = {} for index in range(len(self.node_context)): node = self.node_context[index] if self.chat_record is not None and self.chat_record.details is not None and self.start_node: details = self.chat_record.details.get(node.runtime_node_id) if details is not None and self.start_node.runtime_node_id != node.runtime_node_id: details_result[node.runtime_node_id] = details continue details = get_details(node, index) details['node_id'] = node.id details['up_node_id_list'] = node.up_node_id_list details['runtime_node_id'] = node.runtime_node_id details_result[node.runtime_node_id] = details return details_result def get_record_answer_list(self): answer_text_list = self.get_answer_text_list() return reduce(lambda pre, _n: [*pre, *_n], answer_text_list, []) def get_answer_text_list(self): result = [] answer_list = reduce(lambda x, y: [*x, *y], [n.get_answer_list() for n in self.node_context if n.get_answer_list() is not None], []) up_node = None for index in range(len(answer_list)): current_answer = answer_list[index] if len(current_answer.content) > 0: if up_node is None or current_answer.view_type == 'single_view' or ( current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'): result.append([current_answer]) else: if len(result) > 0: exec_index = len(result) - 1 if isinstance(result[exec_index], list): result[exec_index].append(current_answer) else: result.insert(0, [current_answer]) up_node = current_answer if len(result) == 0: # 如果没有响应 就响应一个空数据 return [[]] return [[item.to_dict() for item in r] for r in result] @staticmethod def dependent_node(edge, node): up_node_id = edge.sourceNodeId if not node.node_chunk.is_end(): return False if node.id == up_node_id: if node.context.get('branch_id', None): if edge.sourceAnchorId == f"{node.id}_{node.context.get('branch_id', None)}_right": return True else: return False if node.type == 'form-node': if node.context.get('form_data', None) is not None: return True return False return True def dependent_node_been_executed(self, node_id): """ 判断依赖节点是否都已执行 @param node_id: 需要判断的节点id @return: """ up_edge_list = [edge for edge in self.flow.edges if edge.targetNodeId == node_id] return all( [any([self.dependent_node(edge, node) for node in self.node_context if node.id == edge.sourceNodeId]) for edge in up_edge_list]) def get_next_node_list(self, current_node, current_node_result): """ 获取下一个可执行节点列表 @param current_node: 当前可执行节点 @param current_node_result: 当前可执行节点结果 @return: 可执行节点列表 """ # 判断是否中断执行 if current_node_result.is_interrupt_exec(current_node): return [] node_list = [] next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or [] if current_node_result is not None and current_node_result.is_assertion_result(): for edge_node in next_edge_node_list: edge = edge_node.edge next_node = edge_node.node if ( f"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId): if next_node.properties.get('condition', "AND") == 'AND': if self.dependent_node_been_executed(edge.targetNodeId): up_nodes = self.flow.get_up_nodes(edge.targetNodeId) up_node_id_list = [*current_node.up_node_id_list, current_node.node.id] if up_nodes and len(up_nodes) > 1: up_nodes.sort(key=lambda node: node.id) first = up_nodes[0] up_node_id_list = [n_c for n_c in self.node_context if n_c.node.id == first.id][ 0].up_node_id_list up_node_id_list = [*up_node_id_list, first.id] node_list.append( self.get_node_cls_by_id(edge.targetNodeId, up_node_id_list)) else: node_list.append( self.get_node_cls_by_id(edge.targetNodeId, [*current_node.up_node_id_list, current_node.node.id])) else: for edge_node in next_edge_node_list: edge = edge_node.edge if edge.sourceNodeId + '_right' == edge.sourceAnchorId: next_node = edge_node.node if next_node.properties.get('condition', "AND") == 'AND': if self.dependent_node_been_executed(edge.targetNodeId): up_nodes = self.flow.get_up_nodes(edge.targetNodeId) up_node_id_list = [*current_node.up_node_id_list, current_node.node.id] if up_nodes and len(up_nodes) > 1: up_nodes.sort(key=lambda node: node.id) first = up_nodes[0] up_node_id_list = [n_c for n_c in self.node_context if n_c.node.id == first.id][ 0].up_node_id_list up_node_id_list = [*up_node_id_list, first.id] node_list.append( self.get_node_cls_by_id(edge.targetNodeId, up_node_id_list)) else: node_list.append( self.get_node_cls_by_id(edge.targetNodeId, [*current_node.up_node_id_list, current_node.node.id])) return node_list def get_reference_field(self, node_id: str, fields: List[str]): """ @param node_id: 节点id @param fields: 字段 @return: """ if node_id == 'global': return INode.get_field(self.context, fields) elif node_id == 'chat': return INode.get_field(self.chat_context, fields) else: node = self.get_node_by_id(node_id) if node: return node.get_reference_field(fields) return None def get_workflow_content(self): context = { 'global': self.context, 'chat': self.chat_context } for node in self.node_context: context[node.id] = node.context return context def reset_prompt(self, prompt: str): placeholder = "{}" for field in self.field_list: globeLabel = f"{field.get('node_name')}.{field.get('value')}" globeValue = f"context.get('{field.get('node_id')}',{placeholder}).get('{field.get('value', '')}','')" prompt = prompt.replace(globeLabel, globeValue) for field in self.global_field_list: globeLabel = f"全局变量.{field.get('value')}" globeLabelNew = f"global.{field.get('value')}" globeValue = f"context.get('global').get('{field.get('value', '')}','')" prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue) for field in self.chat_field_list: chatLabel = f"chat.{field.get('value')}" chatValue = f"context.get('chat').get('{field.get('value', '')}','')" prompt = prompt.replace(chatLabel, chatValue) return prompt def generate_prompt(self, prompt: str): """ 格式化生成提示词 @param prompt: 提示词信息 @return: 格式化后的提示词 """ context = self.get_workflow_content() prompt = self.reset_prompt(prompt) prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2') value = prompt_template.format(context=context) return value def get_start_node(self): """ 获取启动节点 @return: """ start_node_list = [node for node in self.flow.nodes if node.type == 'start-node'] return start_node_list[0] def get_base_node(self): """ 获取基础节点 @return: """ base_node_list = [node for node in self.flow.nodes if node.type == 'base-node'] return base_node_list[0] def get_node_cls_by_id(self, node_id, up_node_id_list=None, get_node_params=lambda node: node.properties.get('node_data')): for node in self.flow.nodes: if node.id == node_id: node_instance = get_node(node.type, self.flow.workflow_mode)(node, self.params, self, up_node_id_list, get_node_params) return node_instance return None def get_node_by_id(self, node_id): for node in self.node_context: if node.id == node_id: return node return None def get_node_reference(self, reference_address: Dict): node = self.get_node_by_id(reference_address.get('node_id')) return node.context[reference_address.get('node_field')] def get_params_serializer_class(self): return FlowParamsSerializer def get_source_type(self): return "APPLICATION" def get_source_id(self): return self.params.get('application_id') ================================================ FILE: apps/application/migrations/0001_initial.py ================================================ # Generated by Django 5.2.4 on 2025-07-14 11:45 from django.db.models import QuerySet import application.models.application import application.models.application_chat import common.encoder.encoder import django.contrib.postgres.fields import django.db.models.deletion import mptt.fields import uuid_utils.compat from django.db import migrations, models def insert_default_data(apps, schema_editor): # 创建一个根模块(没有父节点) QuerySet(application.models.application.ApplicationFolder).create(id='default', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default') class Migration(migrations.Migration): initial = True dependencies = [ ('knowledge', '0001_initial'), ('models_provider', '0001_initial'), ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='Application', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('is_publish', models.BooleanField(default=False, verbose_name='是否发布')), ('name', models.CharField(db_index=True, max_length=128, verbose_name='应用名称')), ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')), ('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')), ('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')), ('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')), ('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')), ('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), ('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), ('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')), ('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')), ('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中', max_length=102400, null=True, verbose_name='问题优化提示词')), ('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')), ('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')), ('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')), ('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')), ('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')), ('clean_time', models.IntegerField(default=180, verbose_name='清理时间')), ('publish_time', models.DateTimeField(blank=True, default=None, null=True, verbose_name='发布时间')), ('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')), ('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')), ('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')), ('stt_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='models_provider.model')), ('tts_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='models_provider.model')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'application', }, ), migrations.CreateModel( name='ApplicationAccessToken', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')), ('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')), ('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')), ('access_num', models.IntegerField(default=100, verbose_name='访问次数')), ('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')), ('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')), ('show_source', models.BooleanField(default=False, verbose_name='是否显示知识来源')), ('show_exec', models.BooleanField(default=False, verbose_name='是否显示执行详情')), ('authentication', models.BooleanField(default=False, verbose_name='是否需要认证')), ('authentication_value', models.JSONField(default=dict, verbose_name='认证的值')), ('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')), ], options={ 'db_table': 'application_access_token', }, ), migrations.CreateModel( name='ApplicationApiKey', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('is_active', models.BooleanField(default=True, verbose_name='是否开启')), ('allow_cross_domain', models.BooleanField(default=False, verbose_name='是否允许跨域')), ('cross_domain_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表')), ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')), ], options={ 'db_table': 'application_api_key', }, ), migrations.CreateModel( name='ApplicationFolder', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')), ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('lft', models.PositiveIntegerField(editable=False)), ('rght', models.PositiveIntegerField(editable=False)), ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), ('level', models.PositiveIntegerField(editable=False)), ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='application.applicationfolder')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'application_folder', }, ), migrations.AddField( model_name='application', name='folder', field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='application.applicationfolder', verbose_name='文件夹id'), ), migrations.CreateModel( name='ApplicationKnowledgeMapping', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('application', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='application.application')), ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')), ], options={ 'db_table': 'application_knowledge_mapping', }, ), migrations.CreateModel( name='ApplicationVersion', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')), ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')), ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('application_name', models.CharField(max_length=128, verbose_name='应用名称')), ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')), ('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')), ('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')), ('model_id', models.UUIDField(blank=True, null=True, verbose_name='大语言模型')), ('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')), ('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')), ('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), ('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), ('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')), ('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')), ('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中', max_length=102400, null=True, verbose_name='问题优化提示词')), ('tts_model_id', models.UUIDField(blank=True, null=True, verbose_name='文本转语音模型id')), ('stt_model_id', models.UUIDField(blank=True, null=True, verbose_name='语音转文本模型id')), ('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')), ('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')), ('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')), ('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')), ('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')), ('clean_time', models.IntegerField(default=180, verbose_name='清理时间')), ('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')), ('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')), ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'application_version', }, ), migrations.CreateModel( name='Chat', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('abstract', models.CharField(max_length=1024, verbose_name='摘要')), ('chat_user_id', models.CharField(default=None, null=True, verbose_name='对话用户id')), ('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='客户端类型')), ('is_deleted', models.BooleanField(default=False, verbose_name='逻辑删除')), ('asker', models.JSONField(default=application.models.application_chat.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者')), ('meta', models.JSONField(default=dict, verbose_name='元数据')), ('star_num', models.IntegerField(default=0, verbose_name='点赞数量')), ('trample_num', models.IntegerField(default=0, verbose_name='点踩数量')), ('chat_record_count', models.IntegerField(default=0, verbose_name='对话次数')), ('mark_sum', models.IntegerField(default=0, verbose_name='标记数量')), ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')), ], options={ 'db_table': 'application_chat', }, ), migrations.CreateModel( name='ChatRecord', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')), ('problem_text', models.CharField(max_length=10240, verbose_name='问题')), ('answer_text', models.CharField(max_length=40960, verbose_name='答案')), ('answer_text_list', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')), ('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')), ('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')), ('const', models.IntegerField(default=0, verbose_name='总费用')), ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情')), ('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')), ('run_time', models.FloatField(default=0, verbose_name='运行时长')), ('index', models.IntegerField(verbose_name='对话下标')), ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.chat')), ], options={ 'db_table': 'application_chat_record', }, ), migrations.CreateModel( name='ApplicationChatUserStats', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('chat_user_id', models.UUIDField(default=uuid_utils.compat.uuid7, verbose_name='对话用户id')), ('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='对话用户类型')), ('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')), ('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')), ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')), ], options={ 'db_table': 'application_chat_user_stats', 'indexes': [models.Index(fields=['application_id', 'chat_user_id'], name='application_applica_1652ba_idx')], }, ), migrations.RunPython(insert_default_data) ] ================================================ FILE: apps/application/migrations/0002_application_simple_mcp.py ================================================ # Generated by Django 5.2.4 on 2025-09-08 03:16 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0001_initial'), ] operations = [ migrations.AddField( model_name='application', name='mcp_enable', field=models.BooleanField(default=False, verbose_name='MCP否启用'), ), migrations.AddField( model_name='application', name='mcp_servers', field=models.JSONField(default=dict, verbose_name='MCP服务列表'), ), migrations.AddField( model_name='application', name='mcp_source', field=models.CharField(default='referencing', max_length=20, verbose_name='MCP Source'), ), migrations.AddField( model_name='application', name='mcp_tool_ids', field=models.JSONField(default=list, verbose_name='MCP工具ID列表'), ), migrations.AddField( model_name='application', name='tool_enable', field=models.BooleanField(default=False, verbose_name='工具是否启用'), ), migrations.AddField( model_name='application', name='tool_ids', field=models.JSONField(default=list, verbose_name='工具ID列表'), ), migrations.AddField( model_name='application', name='mcp_output_enable', field=models.BooleanField(default=True, verbose_name='MCP输出是否启用'), ), migrations.AddField( model_name='applicationversion', name='mcp_enable', field=models.BooleanField(default=False, verbose_name='MCP否启用'), ), migrations.AddField( model_name='applicationversion', name='mcp_servers', field=models.JSONField(default=dict, verbose_name='MCP服务列表'), ), migrations.AddField( model_name='applicationversion', name='mcp_source', field=models.CharField(default='referencing', max_length=20, verbose_name='MCP Source'), ), migrations.AddField( model_name='applicationversion', name='mcp_tool_ids', field=models.JSONField(default=list, verbose_name='MCP工具ID列表'), ), migrations.AddField( model_name='applicationversion', name='tool_enable', field=models.BooleanField(default=False, verbose_name='工具是否启用'), ), migrations.AddField( model_name='applicationversion', name='tool_ids', field=models.JSONField(default=list, verbose_name='工具ID列表'), ), migrations.AddField( model_name='applicationversion', name='mcp_output_enable', field=models.BooleanField(default=True, verbose_name='MCP输出是否启用'), ), ] ================================================ FILE: apps/application/migrations/0003_application_stt_model_params_setting_and_more.py ================================================ # Generated by Django 5.2.4 on 2025-09-16 08:10 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0002_application_simple_mcp'), ] operations = [ migrations.AddField( model_name='application', name='stt_model_params_setting', field=models.JSONField(default=dict, verbose_name='STT模型参数相关设置'), ), migrations.AddField( model_name='applicationversion', name='stt_model_params_setting', field=models.JSONField(default=dict, verbose_name='STT模型参数相关设置'), ), ] ================================================ FILE: apps/application/migrations/0004_application_application_enable_and_more.py ================================================ # Generated by Django 5.2.9 on 2025-12-16 08:02 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0003_application_stt_model_params_setting_and_more'), ] operations = [ migrations.AddField( model_name='application', name='application_enable', field=models.BooleanField(default=False, verbose_name='应用是否启用'), ), migrations.AddField( model_name='application', name='application_ids', field=models.JSONField(default=list, verbose_name='应用ID列表'), ), migrations.AddField( model_name='applicationversion', name='application_enable', field=models.BooleanField(default=False, verbose_name='应用是否启用'), ), migrations.AddField( model_name='applicationversion', name='application_ids', field=models.JSONField(default=list, verbose_name='应用ID列表'), ), ] ================================================ FILE: apps/application/migrations/0005_chatrecord_vote_other_content_chatrecord_vote_reason.py ================================================ # Generated by Django 5.2.8 on 2025-12-23 10:28 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0004_application_application_enable_and_more'), ] operations = [ migrations.AddField( model_name='chatrecord', name='vote_other_content', field=models.CharField(default='', max_length=1024, verbose_name='其他原因'), ), migrations.AddField( model_name='chatrecord', name='vote_reason', field=models.CharField(blank=True, choices=[('accurate', '内容准确'), ('complete', '内容完善'), ('inaccurate', '内容不准确'), ('incomplete', '内容不完善'), ('other', '其他')], max_length=50, null=True, verbose_name='投票原因'), ), ] ================================================ FILE: apps/application/migrations/0006_application_file_clean_time.py ================================================ # Generated by Django 5.2.9 on 2026-01-14 06:51 from django.db import migrations, models def insert_default_data(apps, schema_editor): Application = apps.get_model('application', 'Application') # 批量修改 把所有的file_clean_time的值设置成clean_time的值 Application.objects.all().update(file_clean_time=models.F('clean_time')) class Migration(migrations.Migration): dependencies = [ ('application', '0005_chatrecord_vote_other_content_chatrecord_vote_reason'), ] operations = [ migrations.AddField( model_name='application', name='file_clean_time', field=models.IntegerField(default=180, verbose_name='文件清理时间'), ), migrations.RunPython(insert_default_data) ] ================================================ FILE: apps/application/migrations/0007_applicationapikey_expire_time_and_more.py ================================================ # Generated by Django 5.2.8 on 2026-01-16 06:10 import django.utils.timezone from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0006_application_file_clean_time'), ] operations = [ migrations.AddField( model_name='applicationapikey', name='expire_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='过期时间'), ), migrations.AddField( model_name='applicationapikey', name='is_permanent', field=models.BooleanField(default=True, verbose_name='是否永久'), ), ] ================================================ FILE: apps/application/migrations/0008_chat_ip_address_chat_source_chatrecord_ip_address_and_more.py ================================================ # Generated by Django 5.2.8 on 2026-01-19 07:30 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0007_applicationapikey_expire_time_and_more'), ] operations = [ migrations.AddField( model_name='chat', name='ip_address', field=models.CharField(default='', max_length=128, verbose_name='ip地址'), ), migrations.AddField( model_name='chat', name='source', field=models.JSONField(default=dict, verbose_name='来源'), ), migrations.AddField( model_name='chatrecord', name='ip_address', field=models.CharField(default='', max_length=128, verbose_name='ip地址'), ), migrations.AddField( model_name='chatrecord', name='source', field=models.JSONField(default=dict, verbose_name='来源'), ), ] ================================================ FILE: apps/application/migrations/0009_clean_application_knowledge_mapping.py ================================================ from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('application', '0008_chat_ip_address_chat_source_chatrecord_ip_address_and_more'), ('system_manage', '0005_resourcemapping'), ] operations = [ migrations.RunSQL( "DELETE FROM application_knowledge_mapping;", reverse_sql=migrations.RunSQL.noop ), ] ================================================ FILE: apps/application/migrations/0010_chatsharelink.py ================================================ # Generated by Django 5.2.9 on 2026-02-09 02:39 import django.contrib.postgres.fields import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0009_clean_application_knowledge_mapping'), ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='ChatShareLink', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('share_type', models.CharField(choices=[('PUBLIC', 'public'), ('PRIVATE', 'private')], default='PUBLIC', max_length=20)), ('chat_record_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), size=None)), ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')), ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.chat')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'application_chat_share_link', }, ), ] ================================================ FILE: apps/application/migrations/0011_application_skill_tool_ids.py ================================================ # Generated by Django 5.2.11 on 2026-02-27 07:03 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('application', '0010_chatsharelink'), ] operations = [ migrations.AddField( model_name='application', name='skill_tool_ids', field=models.JSONField(default=list, verbose_name='技能ID列表'), ), migrations.AddField( model_name='applicationversion', name='skill_tool_ids', field=models.JSONField(default=list, verbose_name='技能ID列表'), ), ] ================================================ FILE: apps/application/migrations/0012_remove_applicationapikey_user.py ================================================ # Generated by Django 5.2.11 on 2026-03-18 09:18 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('application', '0011_application_skill_tool_ids'), ] operations = [ migrations.RemoveField( model_name='applicationapikey', name='user', ), ] ================================================ FILE: apps/application/migrations/__init__.py ================================================ ================================================ FILE: apps/application/models/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/5/7 15:14 @desc: """ from .application import * from .application_access_token import * from .application_chat import * from .application_api_key import * ================================================ FILE: apps/application/models/application.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application.py @date:2025/5/7 15:29 @desc: """ import uuid_utils.compat as uuid from django.db import models from mptt.fields import TreeForeignKey from mptt.models import MPTTModel from common.mixins.app_model_mixin import AppModelMixin from knowledge.models import Knowledge from models_provider.models import Model from users.models import User class ApplicationFolder(MPTTModel, AppModelMixin): id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") name = models.CharField(max_length=64, verbose_name="文件夹名称", db_index=True) desc = models.CharField(max_length=200, null=True, blank=True, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children') class Meta: db_table = "application_folder" class MPTTMeta: order_insertion_by = ['name'] class ApplicationTypeChoices(models.TextChoices): """订单类型""" SIMPLE = 'SIMPLE', '简易' WORK_FLOW = 'WORK_FLOW', '工作流' def get_dataset_setting_dict(): return {'top_n': 3, 'similarity': 0.6, 'max_paragraph_char_number': 5000, 'search_mode': 'embedding', 'no_references_setting': { 'status': 'ai_questioning', 'value': '{question}' }} def get_model_setting_dict(): return { 'prompt': Application.get_default_model_prompt(), 'no_references_prompt': '{question}', 'reasoning_content_start': '', 'reasoning_content_end': '', 'reasoning_content_enable': False, } class Application(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) folder = models.ForeignKey(ApplicationFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='default') is_publish = models.BooleanField(verbose_name="是否发布", default=False) name = models.CharField(max_length=128, verbose_name="应用名称", db_index=True) desc = models.CharField(max_length=512, verbose_name="引用描述", default="") prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") dialogue_number = models.IntegerField(default=0, verbose_name="会话数量") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) knowledge_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict) model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict) model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) tts_model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) stt_model_params_setting = models.JSONField(verbose_name="STT模型参数相关设置", default=dict) problem_optimization = models.BooleanField(verbose_name="问题优化", default=False) icon = models.CharField(max_length=256, verbose_name="应用icon", default="./favicon.ico") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, default=ApplicationTypeChoices.SIMPLE, max_length=256) problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True, null=True, default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中") tts_model = models.ForeignKey(Model, related_name='tts_model_id', on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) stt_model = models.ForeignKey(Model, related_name='stt_model_id', on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") tts_autoplay = models.BooleanField(verbose_name="自动播放", default=False) stt_autosend = models.BooleanField(verbose_name="自动发送", default=False) clean_time = models.IntegerField(verbose_name="清理时间", default=180) publish_time = models.DateTimeField(verbose_name="发布时间", default=None, null=True, blank=True) file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False) file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict) mcp_enable = models.BooleanField(verbose_name="MCP否启用", default=False) mcp_tool_ids = models.JSONField(verbose_name="MCP工具ID列表", default=list) mcp_servers = models.JSONField(verbose_name="MCP服务列表", default=dict) mcp_source = models.CharField(verbose_name="MCP Source", max_length=20, default="referencing") tool_enable = models.BooleanField(verbose_name="工具是否启用", default=False) tool_ids = models.JSONField(verbose_name="工具ID列表", default=list) application_enable = models.BooleanField(verbose_name="应用是否启用", default=False) application_ids = models.JSONField(verbose_name="应用ID列表", default=list) skill_tool_ids = models.JSONField(verbose_name="技能ID列表", default=list) mcp_output_enable = models.BooleanField(verbose_name="MCP输出是否启用", default=True) file_clean_time = models.IntegerField(verbose_name="文件清理时间", default=180) @staticmethod def get_default_model_prompt(): return ('已知信息:' '\n{data}' '\n回答要求:' '\n- 如果你不知道答案或者没有从获取答案,请回答“没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作”。' '\n- 避免提及你是从中获得的知识。' '\n- 请保持答案与中描述的一致。' '\n- 请使用markdown 语法优化答案的格式。' '\n- 中的图片链接、链接地址和脚本语言请完整返回。' '\n- 请使用与问题相同的语言来回答。' '\n问题:' '\n{question}') class Meta: db_table = "application" class ApplicationKnowledgeMapping(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") application = models.ForeignKey(Application, on_delete=models.DO_NOTHING) knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING) class Meta: db_table = "application_knowledge_mapping" class ApplicationVersion(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") application = models.ForeignKey(Application, on_delete=models.CASCADE) name = models.CharField(verbose_name="版本名称", max_length=128, default="") publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True) publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) application_name = models.CharField(max_length=128, verbose_name="应用名称") desc = models.CharField(max_length=512, verbose_name="引用描述", default="") prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") dialogue_number = models.IntegerField(default=0, verbose_name="会话数量") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) model_id = models.UUIDField(verbose_name="大语言模型", blank=True, null=True) knowledge_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict) model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict) model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) tts_model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) stt_model_params_setting = models.JSONField(verbose_name="STT模型参数相关设置", default=dict) problem_optimization = models.BooleanField(verbose_name="问题优化", default=False) icon = models.CharField(max_length=256, verbose_name="应用icon", default="./favicon.ico") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, default=ApplicationTypeChoices.SIMPLE, max_length=256) problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True, null=True, default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中") tts_model_id = models.UUIDField(verbose_name="文本转语音模型id", blank=True, null=True) stt_model_id = models.UUIDField(verbose_name="语音转文本模型id", blank=True, null=True) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") tts_autoplay = models.BooleanField(verbose_name="自动播放", default=False) stt_autosend = models.BooleanField(verbose_name="自动发送", default=False) clean_time = models.IntegerField(verbose_name="清理时间", default=180) file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False) file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict) mcp_enable = models.BooleanField(verbose_name="MCP否启用", default=False) mcp_tool_ids = models.JSONField(verbose_name="MCP工具ID列表", default=list) mcp_servers = models.JSONField(verbose_name="MCP服务列表", default=dict) mcp_source = models.CharField(verbose_name="MCP Source", max_length=20, default="referencing") tool_enable = models.BooleanField(verbose_name="工具是否启用", default=False) tool_ids = models.JSONField(verbose_name="工具ID列表", default=list) application_enable = models.BooleanField(verbose_name="应用是否启用", default=False) application_ids = models.JSONField(verbose_name="应用ID列表", default=list) skill_tool_ids = models.JSONField(verbose_name="技能ID列表", default=list) mcp_output_enable = models.BooleanField(verbose_name="MCP输出是否启用", default=True) class Meta: db_table = "application_version" ================================================ FILE: apps/application/models/application_access_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_access_token.py @date:2025/5/27 9:55 @desc: """ from django.contrib.postgres.fields import ArrayField from django.db import models from application.models.application import Application from common.mixins.app_model_mixin import AppModelMixin class ApplicationAccessToken(AppModelMixin): """ 应用认证token """ application = models.OneToOneField(Application, primary_key=True, on_delete=models.CASCADE, verbose_name="应用id") access_token = models.CharField(max_length=128, verbose_name="用户公开访问 认证token", unique=True) is_active = models.BooleanField(default=True, verbose_name="是否开启公开访问") access_num = models.IntegerField(default=100, verbose_name="访问次数") white_active = models.BooleanField(default=False, verbose_name="是否开启白名单") white_list = ArrayField(verbose_name="白名单列表", base_field=models.CharField(max_length=128, blank=True) , default=list) show_source = models.BooleanField(default=False, verbose_name="是否显示知识来源") show_exec = models.BooleanField(default=False, verbose_name="是否显示执行详情") authentication = models.BooleanField(default=False, verbose_name="是否需要认证") authentication_value = models.JSONField(verbose_name="认证的值", default=dict) language = models.CharField(max_length=10, verbose_name="语言", default=None, null=True) class Meta: db_table = "application_access_token" ================================================ FILE: apps/application/models/application_api_key.py ================================================ import uuid_utils.compat as uuid from django.contrib.postgres.fields import ArrayField from django.db import models from django.utils import timezone from application.models import Application from common.mixins.app_model_mixin import AppModelMixin class ApplicationApiKey(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") secret_key = models.CharField(max_length=1024, verbose_name="秘钥", unique=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id") is_active = models.BooleanField(default=True, verbose_name="是否开启") allow_cross_domain = models.BooleanField(default=False, verbose_name="是否允许跨域") cross_domain_list = ArrayField(verbose_name="跨域列表", base_field=models.CharField(max_length=128, blank=True) , default=list) expire_time = models.DateTimeField(verbose_name="过期时间", default=timezone.now) is_permanent = models.BooleanField(default=True, verbose_name="是否永久") class Meta: db_table = "application_api_key" ================================================ FILE: apps/application/models/application_chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat_log.py @date:2025/5/29 17:12 @desc: """ import uuid_utils.compat as uuid from django.contrib.postgres.fields import ArrayField from django.db import models from django.utils.translation import gettext as _ from langchain_core.messages import HumanMessage, AIMessage from application.models import Application from common.encoder.encoder import SystemEncoder from common.mixins.app_model_mixin import AppModelMixin from users.models import User class ChatUserType(models.TextChoices): ANONYMOUS_USER = "ANONYMOUS_USER", '匿名用户' CHAT_USER = "CHAT_USER", "对话用户" SYSTEM_API_KEY = "SYSTEM_API_KEY", "系统API_KEY" APPLICATION_API_KEY = "APPLICATION_API_KEY", "应用API_KEY" PLATFORM_USER = "PLATFORM_USER", "平台用户" def default_asker(): return {'username': '游客'} class Chat(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") application = models.ForeignKey(Application, on_delete=models.CASCADE) abstract = models.CharField(max_length=1024, verbose_name="摘要") chat_user_id = models.CharField(verbose_name="对话用户id", default=None, null=True) chat_user_type = models.CharField(max_length=64, verbose_name="客户端类型", choices=ChatUserType.choices, default=ChatUserType.ANONYMOUS_USER) is_deleted = models.BooleanField(verbose_name="逻辑删除", default=False) asker = models.JSONField(verbose_name="访问者", default=default_asker, encoder=SystemEncoder) meta = models.JSONField(verbose_name="元数据", default=dict) star_num = models.IntegerField(verbose_name="点赞数量", default=0) trample_num = models.IntegerField(verbose_name="点踩数量", default=0) chat_record_count = models.IntegerField(verbose_name="对话次数", default=0) mark_sum = models.IntegerField(verbose_name="标记数量", default=0) source = models.JSONField(verbose_name="来源", default=dict) ip_address = models.CharField(max_length=128, verbose_name="ip地址", default='') class Meta: db_table = "application_chat" class VoteChoices(models.TextChoices): """订单类型""" UN_VOTE = "-1", '未投票' STAR = "0", '赞同' TRAMPLE = "1", '反对' class VoteReasonChoices(models.TextChoices): ACCURATE = 'accurate', '内容准确' COMPLETE = 'complete', '内容完善' INACCURATE = 'inaccurate', '内容不准确' INCOMPLETE = 'incomplete', '内容不完善' OTHER = 'other', '其他' class ShareLinkType(models.TextChoices): PUBLIC = "PUBLIC", 'public' PRIVATE = "PRIVATE", 'private' class ChatSourceChoices(models.TextChoices): ONLINE = "ONLINE", "线上使用" API_CALL = "API_CALL", "API调用" ENTERPRISE_WECHAT = "ENTERPRISE_WECHAT", "企业微信" WECHAT_PUBLIC_ACCOUNT = "WECHAT_PUBLIC_ACCOUNT", "微信公众号" LARK = "LARK", "飞书" DINGTALK = "DINGTALK", "钉钉" ENTERPRISE_WECHAT_ROBOT = "ENTERPRISE_WECHAT_ROBOT", "企业微信机器人" TRIGGER = "TRIGGER", "触发器" SLACK = "SLACK", "Slack" class ChatRecord(AppModelMixin): """ 对话日志 详情 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") chat = models.ForeignKey(Chat, on_delete=models.CASCADE) vote_status = models.CharField(verbose_name='投票', max_length=10, choices=VoteChoices.choices, default=VoteChoices.UN_VOTE) vote_reason = models.CharField(verbose_name='投票原因', max_length=50, choices=VoteReasonChoices.choices, null=True, blank=True) vote_other_content = models.CharField(verbose_name='其他原因', max_length=1024, default='') problem_text = models.CharField(max_length=10240, verbose_name="问题") answer_text = models.CharField(max_length=40960, verbose_name="答案") answer_text_list = ArrayField(verbose_name="改进标注列表", base_field=models.JSONField() , default=list) message_tokens = models.IntegerField(verbose_name="请求token数量", default=0) answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0) const = models.IntegerField(verbose_name="总费用", default=0) details = models.JSONField(verbose_name="对话详情", default=dict, encoder=SystemEncoder) improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表", base_field=models.UUIDField(max_length=128, blank=True) , default=list) run_time = models.FloatField(verbose_name="运行时长", default=0) index = models.IntegerField(verbose_name="对话下标") source = models.JSONField(verbose_name="来源", default=dict) ip_address = models.CharField(max_length=128, verbose_name="ip地址", default='') def get_human_message(self): if 'problem_padding' in self.details: return HumanMessage(content=self.details.get('problem_padding').get('padding_problem_text')) return HumanMessage(content=self.problem_text) def get_ai_message(self): answer_text = self.answer_text if answer_text is None or len(str(answer_text).strip()) == 0: answer_text = _( 'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ') return AIMessage(content=answer_text) def get_node_details_runtime_node_id(self, runtime_node_id): return self.details.get(runtime_node_id, None) class Meta: db_table = "application_chat_record" class ApplicationChatUserStats(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") chat_user_id = models.UUIDField(max_length=128, default=uuid.uuid7, verbose_name="对话用户id") chat_user_type = models.CharField(max_length=64, verbose_name="对话用户类型", choices=ChatUserType.choices, default=ChatUserType.ANONYMOUS_USER) application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id") access_num = models.IntegerField(default=0, verbose_name="访问总次数次数") intraday_access_num = models.IntegerField(default=0, verbose_name="当日访问次数") class Meta: db_table = "application_chat_user_stats" indexes = [ models.Index(fields=['application_id', 'chat_user_id']), ] class ChatShareLink(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") chat = models.ForeignKey(Chat, on_delete=models.CASCADE) application = models.ForeignKey(Application,on_delete=models.CASCADE) share_type = models.CharField(max_length=20, choices=ShareLinkType.choices, default=ShareLinkType.PUBLIC) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) chat_record_ids = ArrayField(base_field=models.UUIDField(max_length=128)) class Meta: db_table = "application_chat_share_link" ================================================ FILE: apps/application/serializers/__init__.py ================================================ ================================================ FILE: apps/application/serializers/application.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application.py @date:2025/5/26 17:03 @desc: """ import asyncio import base64 import hashlib import json import os import pickle import re import tempfile import zipfile from functools import reduce from typing import Dict, List import requests import uuid_utils.compat as uuid from django.core import validators from django.db import models, transaction from django.db.models import QuerySet, Q from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from langchain_mcp_adapters.client import MultiServerMCPClient from rest_framework import serializers, status from rest_framework.utils.formatting import lazy_format from application.flow.common import Workflow from application.models.application import Application, ApplicationTypeChoices, \ ApplicationFolder, ApplicationVersion from application.models.application_access_token import ApplicationAccessToken from application.serializers.common import update_resource_mapping_by_application from common import result from common.cache_data.application_access_token_cache import del_application_access_token from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search, native_page_search from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField from common.utils.common import get_file_content, restricted_loads, generate_uuid, _remove_empty_lines, \ bytes_to_uploaded_file from common.utils.logger import maxkb_logger from knowledge.models import Knowledge, KnowledgeScope, File, FileSourceType from knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeModelSerializer from maxkb.conf import PROJECT_DIR from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType from system_manage.models.resource_mapping import ResourceMapping from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from tools.models import Tool, ToolScope, ToolType from tools.serializers.tool import ToolExportModelSerializer from trigger.models import TriggerTask, Trigger from users.models import User from users.serializers.user import is_workspace_manage def get_base_node_work_flow(work_flow): node_list = work_flow.get('nodes') base_node_list = [node for node in node_list if node.get('id') == 'base-node'] if len(base_node_list) > 0: return base_node_list[-1] return None def hand_node(node, update_tool_map): if node.get('type') == 'tool-lib-node': tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '') node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id) if node.get('type') == 'search-knowledge-node': node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = [] if node.get('type') == 'ai-chat-node': node_data = node.get('properties', {}).get('node_data', {}) mcp_tool_ids = node_data.get('mcp_tool_ids') or [] node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in mcp_tool_ids] skill_tool_ids = node_data.get('skill_tool_ids') or [] node_data['skill_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in skill_tool_ids] tool_ids = node_data.get('tool_ids') or [] node_data['tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in tool_ids] if node.get('type') == 'mcp-node': mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '') node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id, mcp_tool_id) class MKInstance: def __init__(self, application: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]): self.application = application self.function_lib_list = function_lib_list self.version = version self.tool_list = tool_list def get_tool_list(self): return [*(self.tool_list or []), *(self.function_lib_list or [])] class ApplicationSerializerModel(serializers.ModelSerializer): class Meta: model = Application fields = "__all__" class NoReferencesChoices(models.TextChoices): """订单类型""" ai_questioning = 'ai_questioning', 'ai回答' designated_answer = 'designated_answer', '指定回答' class NoReferencesSetting(serializers.Serializer): status = serializers.ChoiceField(required=True, choices=NoReferencesChoices.choices, label=_("No reference status")) value = serializers.CharField(required=True, label=_("Prompt word")) class KnowledgeSettingSerializer(serializers.Serializer): top_n = serializers.FloatField(required=True, max_value=10000, min_value=1, label=_("Reference segment number")) similarity = serializers.FloatField(required=True, max_value=1, min_value=0, label=_("Acquaintance")) max_paragraph_char_number = serializers.IntegerField(required=True, min_value=500, max_value=100000, label=_("Maximum number of quoted characters")) search_mode = serializers.CharField(required=True, validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_("The type only supports embedding|keywords|blend"), code=500) ], label=_("Retrieval Mode")) no_references_setting = NoReferencesSetting(required=True, label=_("Segment settings not referenced")) class ModelKnowledgeAssociation(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Model id")) knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True, label=_( "Knowledge base id")), label=_("Knowledge Base List")) def is_valid(self, *, raise_exception=True): super().is_valid(raise_exception=True) model_id = self.data.get('model_id') user_id = self.data.get('user_id') if model_id is not None and len(model_id) > 0: if not QuerySet(Model).filter(id=model_id).exists(): raise AppApiException(500, f'{_("Model does not exist")}【{model_id}】') knowledge_id_list = list(set(self.data.get('knowledge_id_list', []))) exist_knowledge_id_list = [str(knowledge.id) for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list, user_id=user_id)] for knowledge_id in knowledge_id_list: if not exist_knowledge_id_list.__contains__(knowledge_id): raise AppApiException(500, f'{_("The knowledge base id does not exist")}【{knowledge_id}】') class ModelSettingSerializer(serializers.Serializer): prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, label=_("Prompt word")) system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, label=_("Role prompts")) no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True, label=_("No citation segmentation prompt")) reasoning_content_enable = serializers.BooleanField(required=False, label=_("Thinking process switch")) reasoning_content_start = serializers.CharField(required=False, allow_null=True, default="", allow_blank=True, max_length=256, trim_whitespace=False, label=_("The thinking process begins to mark")) reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, default="", max_length=256, trim_whitespace=False, label=_("End of thinking process marker")) class ApplicationCreateSerializer(serializers.Serializer): class ApplicationResponse(serializers.ModelSerializer): class Meta: model = Application fields = "__all__" class WorkflowRequest(serializers.Serializer): name = serializers.CharField(required=True, max_length=64, min_length=1, label=_("Application Name")) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=256, min_length=1, label=_("Application Description")) work_flow = serializers.DictField(required=True, label=_("Workflow Objects")) prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, label=_("Opening remarks")) folder_id = serializers.CharField(required=True, label=_('folder id')) @staticmethod def to_application_model(user_id: str, workspace_id: str, application: Dict): default_workflow = application.get('work_flow') for node in default_workflow.get('nodes'): if node.get('id') == 'base-node': node.get('properties')['node_data']['desc'] = application.get('desc') node.get('properties')['node_data']['name'] = application.get('name') node.get('properties')['node_data']['prologue'] = application.get('prologue') return Application( id=uuid.uuid7(), name=application.get('name'), desc=application.get('desc'), workspace_id=workspace_id, folder_id=application.get('folder_id', application.get('workspace_id')), prologue="", dialogue_number=0, user_id=user_id, model_id=None, knowledge_setting={}, model_setting={}, problem_optimization=False, type=ApplicationTypeChoices.WORK_FLOW, stt_model_enable=application.get('stt_model_enable', False), stt_model_id=application.get('stt_model', None), tts_model_id=application.get('tts_model', None), tts_model_enable=application.get('tts_model_enable', False), tts_model_params_setting=application.get('tts_model_params_setting', {}), tts_type=application.get('tts_type', 'BROWSER'), file_upload_enable=application.get('file_upload_enable', False), file_upload_setting=application.get('file_upload_setting', {}), work_flow=default_workflow ) class SimplateRequest(serializers.Serializer): name = serializers.CharField(required=True, max_length=64, min_length=1, label=_("application name")) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=256, min_length=1, label=_("application describe")) folder_id = serializers.CharField(required=True, label=_('folder id')) model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Model")) dialogue_number = serializers.IntegerField(required=True, min_value=0, max_value=1024, label=_("Historical chat records")) prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=40960, label=_("Opening remarks")) knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), allow_null=True, label=_("Related Knowledge Base")) # 数据集相关设置 knowledge_setting = KnowledgeSettingSerializer(required=True) # 模型相关设置 model_setting = ModelSettingSerializer(required=True) # 问题补全 problem_optimization = serializers.BooleanField(required=True, label=_("Question completion")) problem_optimization_prompt = serializers.CharField(required=False, max_length=102400, label=_("Question completion prompt")) # 应用类型 type = serializers.CharField(required=True, label=_("Application Type"), validators=[ validators.RegexValidator(regex=re.compile("^SIMPLE|WORK_FLOW$"), message=_( "Application type only supports SIMPLE|WORK_FLOW"), code=500) ] ) model_params_setting = serializers.DictField(required=False, label=_('Model parameters')) tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled')) tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID")) tts_type = serializers.CharField(required=False, label=_('Voice playback type')) tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay')) stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled')) stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID')) stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission')) def is_valid(self, *, user_id=None, raise_exception=False): super().is_valid(raise_exception=True) ModelKnowledgeAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'), 'knowledge_id_list': self.data.get('knowledge_id_list')}).is_valid() @staticmethod def to_application_model(user_id: str, workspace_id: str, application: Dict): return Application( id=uuid.uuid7(), name=application.get('name'), desc=application.get('desc'), workspace_id=workspace_id, prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number', 0), user_id=user_id, model_id=application.get('model_id'), folder_id=application.get('folder_id', application.get('workspace_id')), knowledge_setting=application.get('knowledge_setting'), model_setting=application.get('model_setting'), problem_optimization=application.get('problem_optimization'), type=ApplicationTypeChoices.SIMPLE, model_params_setting=application.get('model_params_setting', {}), problem_optimization_prompt=application.get('problem_optimization_prompt', None), stt_model_enable=application.get('stt_model_enable', False), stt_model_id=application.get('stt_model', None), stt_autosend=application.get('stt_autosend', False), tts_model_id=application.get('tts_model', None), tts_model_enable=application.get('tts_model_enable', False), tts_model_params_setting=application.get('tts_model_params_setting', {}), tts_type=application.get('tts_type', 'BROWSER'), file_upload_enable=application.get('file_upload_enable', False), file_upload_setting=application.get('file_upload_setting', {}), work_flow={}, mcp_enable=application.get('mcp_enable', False), mcp_tool_ids=application.get('mcp_tool_ids', []), mcp_servers=application.get('mcp_servers', {}), mcp_source=application.get('mcp_source', 'referencing'), tool_enable=application.get('tool_enable', False), tool_ids=application.get('tool_ids', []), mcp_output_enable=application.get('mcp_output_enable', False), ) class ApplicationQueryRequest(serializers.Serializer): folder_id = serializers.CharField(required=False, label=_("folder id")) name = serializers.CharField(required=False, label=_('Application Name')) desc = serializers.CharField(required=False, label=_("Application Description")) publish_status = serializers.ChoiceField(required=False, label=_("Publish status"), choices=[('published', _("Published")), ('unpublished', _("Unpublished"))]) user_id = serializers.UUIDField(required=False, label=_("User ID")) class ApplicationListResponse(serializers.Serializer): id = serializers.CharField(required=True, label=_("Primary key id"), help_text=_("Primary key id")) name = serializers.CharField(required=True, label=_("Application Name"), help_text=_("Application Name")) desc = serializers.CharField(required=True, label=_("Application Description"), help_text=_("Application Description")) is_publish = serializers.BooleanField(required=True, label=_("Model id"), help_text=_("Model id")) type = serializers.CharField(required=True, label=_("Application type"), help_text=_("Application type")) resource_type = serializers.CharField(required=True, label=_("Resource type"), help_text=_("Resource type")) user_id = serializers.CharField(required=True, label=_('Affiliation user'), help_text=_("Affiliation user")) create_time = serializers.CharField(required=True, label=_('Creation time'), help_text=_("Creation time")) update_time = serializers.CharField(required=True, label=_('Modification time'), help_text=_("Modification time")) class Query(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('Workspace ID')) user_id = serializers.UUIDField(required=True, label=_("User ID")) def get_query_set(self, instance: Dict, workspace_manage: bool, is_x_pack_ee: bool): folder_query_set = QuerySet(ApplicationFolder) application_query_set = QuerySet(Application) workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') desc = instance.get('desc') name = instance.get('name') publish_status = instance.get("publish_status") create_user = instance.get('create_user') if publish_status is not None: is_publish = True if publish_status == "published" else False application_query_set = application_query_set.filter(is_publish=is_publish) if workspace_id is not None: folder_query_set = folder_query_set.filter(workspace_id=workspace_id) application_query_set = application_query_set.filter(workspace_id=workspace_id) folder_id = instance.get('folder_id') if folder_id is not None and folder_id != workspace_id: folder_query_set = folder_query_set.filter(parent=folder_id) application_query_set = application_query_set.filter(folder_id=folder_id) if name is not None: folder_query_set = folder_query_set.filter(name__contains=name) application_query_set = application_query_set.filter(name__contains=name) if desc is not None: folder_query_set = folder_query_set.filter(desc__contains=desc) application_query_set = application_query_set.filter(desc__contains=desc) if create_user is not None: application_query_set = application_query_set.filter(user_id=create_user) application_custom_sql_query_set = application_query_set application_query_set = application_query_set.order_by("-create_time") resource_and_folder_query_set = QuerySet(WorkspaceUserResourcePermission).filter( auth_target_type="APPLICATION", workspace_id=workspace_id, user_id=user_id) return {'application_query_set': application_query_set, 'workspace_user_resource_permission_query_set': resource_and_folder_query_set, } if ( not workspace_manage) else { 'application_query_set': application_query_set, 'application_custom_sql': application_custom_sql_query_set } @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def list(self, instance: Dict): self.is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') user_id = self.data.get("user_id") ApplicationQueryRequest(data=instance).is_valid(raise_exception=True) workspace_manage = is_workspace_manage(user_id, workspace_id) is_x_pack_ee = self.is_x_pack_ee() return native_search(self.get_query_set(instance, workspace_manage, is_x_pack_ee), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_application.sql' if workspace_manage else ( 'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql') ))) def page(self, current_page: int, page_size: int, instance: Dict): self.is_valid(raise_exception=True) ApplicationQueryRequest(data=instance).is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') user_id = self.data.get("user_id") workspace_manage = is_workspace_manage(user_id, workspace_id) is_x_pack_ee = self.is_x_pack_ee() result = native_page_search(current_page, page_size, self.get_query_set(instance, workspace_manage, is_x_pack_ee), get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_application.sql' if workspace_manage else ( 'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql'))), ) return ResourceMappingSerializer().get_resource_count(result) class ApplicationImportRequest(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) folder_id = serializers.CharField(required=True, label=_("Folder ID")) class ApplicationEditSerializer(serializers.Serializer): name = serializers.CharField(required=False, max_length=64, min_length=1, label=_("Application Name")) desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True, label=_("Application Description")) model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model")) dialogue_number = serializers.IntegerField(required=False, min_value=0, max_value=1024, label=_("Historical chat records")) prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, label=_("Opening remarks")) knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), label=_("Related Knowledge Base") ) # 数据集相关设置 knowledge_setting = KnowledgeSettingSerializer(required=False, allow_null=True, label=_("Dataset settings")) # 模型相关设置 model_setting = ModelSettingSerializer(required=False, allow_null=True, label=_("Model setup")) # 问题补全 problem_optimization = serializers.BooleanField(required=False, allow_null=True, label=_("Question completion")) icon = serializers.CharField(required=False, allow_null=True, label=_("Icon")) model_params_setting = serializers.DictField(required=False, label=_('Model parameters')) tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled')) tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID")) tts_type = serializers.CharField(required=False, label=_('Voice playback type')) tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay')) stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled')) stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID')) stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission')) class ApplicationSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) @transaction.atomic def insert(self, instance: Dict): work_flow_template = instance.get('work_flow_template') application_type = instance.get('type') # 处理工作流模板安装逻辑 if work_flow_template: return self.insert_template_workflow(instance) if 'WORK_FLOW' == application_type: r = self.insert_workflow(instance) else: r = self.insert_simple(instance) UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.APPLICATION.value }).auth_resource(str(r.get('id'))) return r def insert_template_workflow(self, instance: Dict): self.is_valid(raise_exception=True) work_flow_template = instance.get('work_flow_template') download_url = work_flow_template.get('downloadUrl') # 查找匹配的版本名称 res = requests.get(download_url, timeout=5) app = ApplicationSerializer( data={'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id')} ).import_({ 'file': bytes_to_uploaded_file(res.content, 'file.mk'), 'folder_id': instance.get('folder_id', instance.get('workspace_id')) }, True) work_flow = app.get('work_flow') for node in work_flow.get('nodes', []): if node.get('type') == 'base-node': node_data = node.get('properties').get('node_data') node_data['name'] = instance.get('name') node_data['desc'] = instance.get('desc') QuerySet(Application).filter(id=app.get('id')).update( name=instance.get('name'), desc=instance.get('desc'), work_flow=work_flow ) try: requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return app def insert_workflow(self, instance: Dict): self.is_valid(raise_exception=True) user_id = self.data.get('user_id') workspace_id = self.data.get('workspace_id') wq = ApplicationCreateSerializer.WorkflowRequest(data=instance) wq.is_valid(raise_exception=True) application_model = wq.to_application_model(user_id, workspace_id, instance) application_model.save() # 插入认证信息 ApplicationAccessToken(application_id=application_model.id, access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save() return ApplicationCreateSerializer.ApplicationResponse(application_model).data @staticmethod def to_application_knowledge_mapping(application_id: str, knowledge_id: str): return ResourceMapping(id=uuid.uuid7(), source_id=application_id, target_id=knowledge_id, source_type="APPLICATION", target_type="KNOWLEDGE") def insert_simple(self, instance: Dict): self.is_valid(raise_exception=True) user_id = self.data.get('user_id') workspace_id = self.data.get("workspace_id") ApplicationCreateSerializer.SimplateRequest(data=instance).is_valid(user_id=user_id, raise_exception=True) application_model = ApplicationCreateSerializer.SimplateRequest.to_application_model(user_id, workspace_id, instance) knowledge_id_list = instance.get('knowledge_id_list', []) application_knowledge_mapping_model_list = [ self.to_application_knowledge_mapping(application_model.id, knowledge_id) for knowledge_id in knowledge_id_list] # 插入应用 application_model.save() # 插入认证信息 ApplicationAccessToken(application_id=application_model.id, access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save() # 插入关联数据 QuerySet(ResourceMapping).bulk_create(application_knowledge_mapping_model_list) return ApplicationCreateSerializer.ApplicationResponse(application_model).data @transaction.atomic def import_(self, instance: dict, is_import_tool, with_valid=True): if with_valid: self.is_valid() ApplicationImportRequest(data=instance).is_valid(raise_exception=True) user_id = self.data.get('user_id') workspace_id = self.data.get("workspace_id") folder_id = instance.get('folder_id') mk_instance_bytes = instance.get('file').read() try: mk_instance = restricted_loads(mk_instance_bytes) except Exception as e: raise AppApiException(1001, _("Unsupported file format")) application = mk_instance.application tool_list = mk_instance.get_tool_list() update_tool_map = {} if len(tool_list) > 0: tool_id_list = reduce(lambda x, y: [*x, *y], [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))] for tool in tool_list], []) # 存在的工具列表 exits_tool_id_list = [str(tool.id) for tool in QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)] # 需要更新的工具集合 update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id'))} tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id')) and not exits_tool_id_list.__contains__( generate_uuid((tool.get('id') + workspace_id or '')))] application_model = self.to_application(application, workspace_id, user_id, update_tool_map, folder_id) tool_model_list = [self.to_tool(f, workspace_id, user_id) for f in tool_list] application_model.save() # 插入授权数据 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.APPLICATION.value }).auth_resource(str(application_model.id)) # 插入认证信息 ApplicationAccessToken(application_id=application_model.id, access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save() if is_import_tool: if len(tool_model_list) > 0: QuerySet(Tool).bulk_create(tool_model_list) UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource_batch([t.id for t in tool_model_list]) return ApplicationCreateSerializer.ApplicationResponse(application_model).data @staticmethod def to_tool(tool, workspace_id, user_id): """ @param workspace_id: @param user_id: 用户id @param tool: 工具 @return: """ # 如果是技能类型的工具,需要将code保存为文件 code = tool.get('code') if tool.get('tool_type') == ToolType.SKILL: skill_file_id = uuid.uuid7() skill_file = File( id=skill_file_id, file_name=f"{tool.get('name')}.zip", source_type=FileSourceType.TOOL, source_id=tool.get('id'), meta={} ) skill_file.save(base64.b64decode(code)) tool['code'] = skill_file_id return Tool(id=tool.get('id'), user_id=user_id, name=tool.get('name'), code=tool.get('code'), template_id=tool.get('template_id'), input_field_list=tool.get('input_field_list'), init_field_list=tool.get('init_field_list'), is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM', scope=ToolScope.WORKSPACE, folder_id=workspace_id, workspace_id=workspace_id) @staticmethod def to_application(application, workspace_id, user_id, update_tool_map, folder_id): work_flow = application.get('work_flow') for node in work_flow.get('nodes', []): hand_node(node, update_tool_map) if node.get('type') == 'loop-node': for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): hand_node(n, update_tool_map) return Application(id=uuid.uuid7(), user_id=user_id, name=application.get('name'), workspace_id=workspace_id, folder_id=folder_id, desc=application.get('desc'), prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number'), knowledge_setting=application.get('knowledge_setting'), model_setting=application.get('model_setting'), model_params_setting=application.get('model_params_setting'), tts_model_params_setting=application.get('tts_model_params_setting'), problem_optimization=application.get('problem_optimization'), icon="./favicon.ico", work_flow=work_flow, type=application.get('type'), problem_optimization_prompt=application.get('problem_optimization_prompt'), tts_model_enable=application.get('tts_model_enable'), stt_model_enable=application.get('stt_model_enable'), tts_type=application.get('tts_type'), clean_time=application.get('clean_time'), file_clean_time=application.get('file_clean_time') or 180, file_upload_enable=application.get('file_upload_enable'), file_upload_setting=application.get('file_upload_setting'), tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in application.get('tool_ids', [])], skill_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in application.get('skill_tool_ids', [])], mcp_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in application.get('mcp_tool_ids', [])], ) class StoreApplication(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True) def get_appstore_templates(self): self.is_valid(raise_exception=True) # 下载zip文件 try: res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5) res.raise_for_status() # 创建临时文件保存zip with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip: temp_zip.write(res.content) temp_zip_path = temp_zip.name try: # 解压zip文件 with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref: # 获取zip中的第一个文件(假设只有一个json文件) json_filename = zip_ref.namelist()[0] json_content = zip_ref.read(json_filename) # 将json转换为字典 tool_store = json.loads(json_content.decode('utf-8')) tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']} filter_apps = [] for tool in tool_store['apps']: if self.data.get('name', '') != '': if self.data.get('name').lower() not in tool.get('name', '').lower(): continue if not tool['downloadUrl'].endswith('.mk'): continue versions = tool.get('versions', []) tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else '' tool['version'] = next( (version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']), ) filter_apps.append(tool) tool_store['apps'] = filter_apps return tool_store finally: # 清理临时文件 os.unlink(temp_zip_path) except Exception as e: maxkb_logger.error(f"fetch appstore tools error: {e}") return {'apps': [], 'additionalProperties': {'tags': []}} class TextToSpeechRequest(serializers.Serializer): text = serializers.CharField(required=True, label=_('Text')) class SpeechToTextRequest(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) class PlayDemoTextRequest(serializers.Serializer): tts_model_id = serializers.UUIDField(required=True, label=_('Text to speech model ID')) async def get_mcp_tools(servers): client = MultiServerMCPClient(servers) return await client.get_tools() class McpServersSerializer(serializers.Serializer): mcp_servers = serializers.JSONField(required=True) class ApplicationOperateSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def get_mcp_servers(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) McpServersSerializer(data=instance).is_valid(raise_exception=True) servers = json.loads(instance.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')) tools = [] for server in servers: tools += [ { 'server': server, 'name': tool.name, 'description': tool.description, 'args_schema': tool.args_schema, } for tool in asyncio.run(get_mcp_tools({server: servers[server]}))] return tools def delete(self, with_valid=True): from trigger.handler.simple_tools import deploy from trigger.serializers.trigger import TriggerModelSerializer if with_valid: self.is_valid() application_id = self.data.get('application_id') QuerySet(ApplicationVersion).filter(application_id=application_id).delete() QuerySet(ResourceMapping).filter( Q(target_id=application_id) | Q(source_id=application_id) ).delete() QuerySet(Application).filter(id=application_id).delete() trigger_ids = list( QuerySet(TriggerTask).filter( source_type="APPLICATION", source_id=application_id ).values('trigger_id').distinct() ) QuerySet(TriggerTask).filter(source_type="APPLICATION", source_id=application_id).delete() for trigger_id in trigger_ids: trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first() if trigger and trigger.is_active: deploy(TriggerModelSerializer(trigger).data, **{}) return True def export(self, with_valid=True): try: if with_valid: self.is_valid() application_id = self.data.get('application_id') application = QuerySet(Application).filter(id=application_id).first() from application.flow.tools import get_tool_id_list tool_id_list = get_tool_id_list(application.work_flow) if len(tool_id_list) > 0: tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) else: tool_list = QuerySet(Tool).filter( id__in=application.tool_ids + application.mcp_tool_ids + application.skill_tool_ids ).exclude(scope=ToolScope.SHARED) # 如果是技能工具,则需要将code字段转换为文件内容的base64字符串 for tool in tool_list: if tool.tool_type == ToolType.SKILL: skill_file = QuerySet(File).filter(id=tool.code).first() if skill_file: tool.code = base64.b64encode(skill_file.get_bytes()).decode('utf-8') application_dict = ApplicationSerializerModel(application).data mk_instance = MKInstance(application_dict, [], 'v2', [ToolExportModelSerializer(tool).data for tool in tool_list]) application_pickle = pickle.dumps(mk_instance) response = HttpResponse(content_type='text/plain', content=application_pickle) response['Content-Disposition'] = f'attachment; filename="{application.name}.mk"' return response except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) @staticmethod def reset_application_version(application_version, application): update_field_dict = { 'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number', 'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting', 'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting', 'tts_model_params_setting': 'tts_model_params_setting', 'stt_model_params_setting': 'stt_model_params_setting', 'problem_optimization': 'problem_optimization', 'icon': 'icon', 'work_flow': 'work_flow', 'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id', 'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable', 'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type', 'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable', 'file_upload_setting': 'file_upload_setting', 'mcp_enable': 'mcp_enable', 'mcp_tool_ids': 'mcp_tool_ids', 'mcp_servers': 'mcp_servers', 'mcp_source': 'mcp_source', 'tool_enable': 'tool_enable', 'tool_ids': 'tool_ids', 'application_enable': 'application_enable', 'application_ids': 'application_ids', 'skill_tool_ids': 'skill_tool_ids', 'mcp_output_enable': 'mcp_output_enable', 'type': 'type' } for (version_field, app_field) in update_field_dict.items(): _v = getattr(application, app_field) setattr(application_version, version_field, _v) @transaction.atomic def publish(self, instance, with_valid=True): if with_valid: self.is_valid() user_id = self.data.get('user_id') workspace_id = self.data.get("workspace_id") user = QuerySet(User).filter(id=user_id).first() application = QuerySet(Application).filter(id=self.data.get("application_id"), workspace_id=workspace_id).first() if application.type == ApplicationTypeChoices.WORK_FLOW: work_flow = application.work_flow if work_flow is None: raise AppApiException(500, _("work_flow is a required field")) Workflow.new_instance(work_flow).is_valid() base_node = get_base_node_work_flow(work_flow) if base_node is not None: node_data = base_node.get('properties').get('node_data') if node_data is not None: application.name = node_data.get('name') application.desc = node_data.get('desc') application.prologue = node_data.get('prologue') application.work_flow = work_flow application.publish_time = timezone.now() application.is_publish = True application.save() work_flow_version = ApplicationVersion(work_flow=application.work_flow, application=application, name=timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'), publish_user_id=user_id, publish_user_name=user.username, workspace_id=workspace_id) self.reset_application_version(work_flow_version, application) work_flow_version.save() access_token = hashlib.md5( str(uuid.uuid7()).encode()).hexdigest()[ 8:24] application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=application.id).first() if application_access_token is None: application_access_token = ApplicationAccessToken(application_id=application.id, access_token=access_token, is_active=True) application_access_token.save() else: access_token = application_access_token.access_token del_application_access_token(access_token) QuerySet(TriggerTask).filter(source_type="APPLICATION", source_id=self.data.get("application_id")).update( is_active=True) return self.one(with_valid=False) @staticmethod def update_work_flow_model(instance): if 'nodes' not in instance.get('work_flow'): return nodes = instance.get('work_flow')['nodes'] for node in nodes: if node['id'] == 'base-node': node_data = node['properties']['node_data'] if 'stt_model_id' in node_data: instance['stt_model_id'] = node_data['stt_model_id'] if 'tts_model_id' in node_data: instance['tts_model_id'] = node_data['tts_model_id'] if 'stt_model_enable' in node_data: instance['stt_model_enable'] = node_data['stt_model_enable'] if 'tts_model_enable' in node_data: instance['tts_model_enable'] = node_data['tts_model_enable'] if 'tts_type' in node_data: instance['tts_type'] = node_data['tts_type'] if 'tts_autoplay' in node_data: instance['tts_autoplay'] = node_data['tts_autoplay'] if 'stt_autosend' in node_data: instance['stt_autosend'] = node_data['stt_autosend'] if 'tts_model_params_setting' in node_data: instance['tts_model_params_setting'] = node_data['tts_model_params_setting'] if 'stt_model_params_setting' in node_data: instance['stt_model_params_setting'] = node_data['stt_model_params_setting'] if 'file_upload_enable' in node_data: instance['file_upload_enable'] = node_data['file_upload_enable'] if 'file_upload_setting' in node_data: instance['file_upload_setting'] = node_data['file_upload_setting'] if 'name' in node_data: instance['name'] = node_data['name'] break knowledge_node_list = ApplicationOperateSerializer.get_search_node(instance.get('work_flow')) for knowledge_node in knowledge_node_list: node_data = knowledge_node.get('properties').get('node_data') # 全部知识库id all_knowledge_id_list = node_data.get('all_knowledge_id_list') or [] # 用户修改的知识库id knowledge_id_list = node_data.get('knowledge_id_list') or [] # 用户可以看到的知识库 knowledge_list = node_data.get('knowledge_list') or [] view_knowledge_id_list = [knowledge.get('id') for knowledge in knowledge_list] other_knowledge_id_list = [knowledge_id for knowledge_id in all_knowledge_id_list if not view_knowledge_id_list.__contains__(knowledge_id)] node_data['knowledge_id_list'] = other_knowledge_id_list + knowledge_id_list def move(self, folder_id: str): self.is_valid(raise_exception=True) application_id = self.data.get("application_id") application = QuerySet(Application).get(id=application_id) application.folder_id = folder_id application.save() return True @transaction.atomic def edit(self, instance: Dict, with_valid=True): if with_valid: self.is_valid() ApplicationEditSerializer(data=instance).is_valid( raise_exception=True) application_id = self.data.get("application_id") application = QuerySet(Application).get(id=application_id) # 处理工作流模板逻辑 if 'work_flow_template' in instance: return self.update_template_workflow(instance, application) if instance.get('model_id') is None or len(instance.get('model_id')) == 0: application.model_id = None else: model = QuerySet(Model).filter( id=instance.get('model_id')).first() if model is None: raise AppApiException(500, _("Model does not exist")) if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0: application.stt_model_id = None else: model = QuerySet(Model).filter( id=instance.get('stt_model_id')).first() if model is None: raise AppApiException(500, _("Model does not exist")) if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0: application.tts_model_id = None else: model = QuerySet(Model).filter( id=instance.get('tts_model_id')).first() if model is None: raise AppApiException(500, _("Model does not exist")) if 'work_flow' in instance: # 修改语音配置相关 self.update_work_flow_model(instance) update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status', 'knowledge_setting', 'model_setting', 'problem_optimization', 'dialogue_number', 'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type', 'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting', 'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting', 'stt_model_params_setting', 'mcp_enable', 'mcp_tool_ids', 'mcp_servers', 'mcp_source', 'tool_enable', 'tool_ids', 'mcp_output_enable', 'application_enable', 'application_ids', 'skill_tool_ids', 'problem_optimization_prompt', 'clean_time', 'file_clean_time', 'folder_id'] for update_key in update_keys: if update_key in instance and instance.get(update_key) is not None: application.__setattr__(update_key, instance.get(update_key)) application.save() # 当前用户可修改关联的知识库列表 application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in self.list_knowledge(with_valid=False)] knowledge_id_list = [] if 'knowledge_id_list' in instance: # 当前用户可修改关联的知识库列表 application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in self.list_knowledge(with_valid=False)] knowledge_id_list = instance.get('knowledge_id_list') for knowledge_id in knowledge_id_list: if not application_knowledge_id_list.__contains__(knowledge_id): message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'), dataset_id=knowledge_id) raise AppApiException(500, str(message)) update_resource_mapping_by_application(application_id, self.get_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id)) return self.one(with_valid=False) def update_template_workflow(self, instance: Dict, app: Application): self.is_valid(raise_exception=True) work_flow_template = instance.get('work_flow_template') download_url = work_flow_template.get('downloadUrl') # 查找匹配的版本名称 res = requests.get(download_url, timeout=5) try: mk_instance = restricted_loads(res.content) except Exception as e: raise AppApiException(1001, _("Unsupported file format")) application = mk_instance.application tool_list = mk_instance.get_tool_list() update_tool_map = {} if len(tool_list) > 0: tool_id_list = reduce(lambda x, y: [*x, *y], [[tool.get('id'), generate_uuid((tool.get('id') + app.workspace_id or ''))] for tool in tool_list], []) # 存在的工具列表 exits_tool_id_list = [str(tool.id) for tool in QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=app.workspace_id)] # 需要更新的工具集合 update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + app.workspace_id or '')) for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id'))} tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id')) and not exits_tool_id_list.__contains__( generate_uuid((tool.get('id') + app.workspace_id or '')))] tool_model_list = [self.to_tool(f, app.workspace_id, self.data.get('user_id')) for f in tool_list] work_flow = application.get('work_flow') for node in work_flow.get('nodes', []): hand_node(node, update_tool_map) if node.get('type') == 'loop-node': for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): hand_node(n, update_tool_map) app.work_flow = work_flow application = mk_instance.application app.name = application.get('name') app.desc = application.get('desc') app.save() if len(tool_model_list) > 0: QuerySet(Tool).bulk_create(tool_model_list) UserResourcePermissionSerializer(data={ 'workspace_id': app.workspace_id, 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource_batch([t.id for t in tool_model_list]) try: requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return self.one(with_valid=False) @staticmethod def to_tool(tool, workspace_id, user_id): return Tool( id=tool.get('id'), user_id=user_id, name=tool.get('name'), code=tool.get('code'), template_id=tool.get('template_id'), input_field_list=tool.get('input_field_list'), init_field_list=tool.get('init_field_list'), is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), scope=ToolScope.WORKSPACE, folder_id=workspace_id, workspace_id=workspace_id ) def one(self, with_valid=True): if with_valid: self.is_valid() application_id = self.data.get("application_id") application = QuerySet(Application).get(id=application_id) available_knowledge_list = self.list_knowledge(with_valid=False) available_knowledge_dict = {knowledge.get('id'): knowledge for knowledge in available_knowledge_list} knowledge_list = [] knowledge_id_list = [] if application.type == 'SIMPLE': mapping_knowledge_list = QuerySet(ResourceMapping).filter(source_id=application_id, source_type="APPLICATION", target_type="KNOWLEDGE") knowledge_list = [available_knowledge_dict.get(str(km.target_id)) for km in mapping_knowledge_list if available_knowledge_dict.__contains__(str(km.target_id))] knowledge_id_list = [k.get('id') for k in knowledge_list] else: self.update_knowledge_node(application.work_flow, available_knowledge_dict) return {**ApplicationSerializerModel(application).data, 'knowledge_id_list': knowledge_id_list, 'knowledge_list': knowledge_list} @staticmethod def get_search_node(work_flow): if work_flow is None: return [] response = [] if 'nodes' in work_flow: for node in work_flow.get('nodes'): if node.get('type', '') == 'search-knowledge-node': response.append(node) if node.get('type') == 'loop-node': r = ApplicationOperateSerializer.get_search_node( node.get('properties', {}).get('node_data', {}).get('loop_body')) for rn in r: response.append(rn) return response def update_knowledge_node(self, workflow, available_knowledge_dict): """ 修改知识库检索节点 数据 定义 all_knowledge_id_list: 所有的关联知识库 knowledge_id_list: 当前用户可看到的关联知识库列表 knowledge_list: 用户 @param workflow: 知识库 @param available_knowledge_dict: 当前用户可用的知识库 @return: """ knowledge_node_list = self.get_search_node(workflow) for search_node in knowledge_node_list: node_data = search_node.get('properties', {}).get('node_data', {}) # 当前知识库关联的所有知识库 knowledge_id_list = node_data.get('knowledge_id_list', []) knowledge_list = [available_knowledge_dict.get(knowledge_id) for knowledge_id in knowledge_id_list if available_knowledge_dict.__contains__(knowledge_id)] node_data['all_knowledge_id_list'] = knowledge_id_list node_data['knowledge_id_list'] = [knowledge.get('id') for knowledge in knowledge_list] node_data['knowledge_list'] = knowledge_list def list_knowledge(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") user_id = self.data.get('user_id') knowledge_workspace_authorization_model = DatabaseModelManage.get_model('knowledge_workspace_authorization') share_knowledge_list = [] if knowledge_workspace_authorization_model is not None: white_list_condition = Q(authentication_type='WHITE_LIST') & Q( workspace_id_list__contains=[workspace_id]) default_condition = ~Q(authentication_type='WHITE_LIST') & ~Q( workspace_id_list__contains=[workspace_id]) # 组合查询 query = white_list_condition | default_condition inner = QuerySet(knowledge_workspace_authorization_model).filter(query) share_knowledge_list = [{**KnowledgeModelSerializer(k).data, 'scope': 'SHARED'} for k in QuerySet(Knowledge).filter(id__in=inner)] workspace_knowledge_list = [{**k, 'scope': 'WORKSPACE'} for k in KnowledgeSerializer.Query( data={ 'workspace_id': workspace_id, 'scope': KnowledgeScope.WORKSPACE, 'user_id': user_id } ).list() if k.get('resource_type') == 'knowledge'] return [*workspace_knowledge_list, *share_knowledge_list] @staticmethod def save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id): # 需要排除已删除的数据集 knowledge_id_list = [knowledge.id for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list)] # 删除已经关联的id QuerySet(ResourceMapping).filter(target_id__in=application_knowledge_id_list, source_id=application_id, source_type='APPLICATION', target_type="KNOWLEDGE").delete() # 插入 QuerySet(ResourceMapping).bulk_create( [ResourceMapping(source_id=application_id, target_id=knowledge_id, source_type='APPLICATION', target_type="KNOWLEDGE") for knowledge_id in knowledge_id_list]) if len(knowledge_id_list) > 0 else None @staticmethod def get_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id): """ @param application_knowledge_id_list: 当前应用可修改的知识库列表 @param knowledge_id_list: 用户修改的知识库列表 @param application_id: 应用id @return: """ # 当前知识库和应用已关联列表 knowledge_application_mapping_list = QuerySet(ResourceMapping).filter(source_id=application_id, source_type='APPLICATION', target_type="KNOWLEDGE", ).exclude( target_id__in=application_knowledge_id_list) edit_knowledge_list = [ResourceMapping(source_id=application_id, target_id=knowledge_id, source_type='APPLICATION', target_type="KNOWLEDGE") for knowledge_id in knowledge_id_list] return list(knowledge_application_mapping_list) + edit_knowledge_list def speech_to_text(self, instance, debug=True, with_valid=True): if with_valid: self.is_valid(raise_exception=True) SpeechToTextRequest(data=instance).is_valid(raise_exception=True) application_id = self.data.get('application_id') if debug: application = QuerySet(Application).filter(id=application_id).first() else: application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by( '-create_time').first() if application.stt_model_enable: model = get_model_instance_by_model_workspace_id(application.stt_model_id, application.workspace_id, **application.stt_model_params_setting) text = model.speech_to_text(instance.get('file')) return text def text_to_speech(self, instance, debug=True, with_valid=True): if with_valid: self.is_valid(raise_exception=True) TextToSpeechRequest(data=instance).is_valid(raise_exception=True) application_id = self.data.get('application_id') if debug: application = QuerySet(Application).filter(id=application_id).first() else: application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by( '-create_time').first() if application.tts_model_enable: model = get_model_instance_by_model_workspace_id(application.tts_model_id, application.workspace_id, **application.tts_model_params_setting) content = _remove_empty_lines(instance.get('text', '')) return model.text_to_speech(content) def play_demo_text(self, instance, with_valid=True): text = '你好,这里是语音播放测试' if with_valid: self.is_valid(raise_exception=True) PlayDemoTextRequest(data=instance).is_valid(raise_exception=True) tts_model_id = instance.pop('tts_model_id') model = get_model_instance_by_model_workspace_id(tts_model_id, self.data.get('workspace_id'), **instance) return model.text_to_speech(text) ================================================ FILE: apps/application/serializers/application_access_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_access_token.py @date:2025/6/9 17:49 @desc: """ import hashlib import uuid_utils.compat as uuid from django.core.cache import cache from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ApplicationAccessToken, Application from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException class AccessTokenEditSerializer(serializers.Serializer): access_token_reset = serializers.BooleanField(required=False, label=_("Reset Token")) is_active = serializers.BooleanField(required=False, label=_("Is it enabled")) access_num = serializers.IntegerField(required=False, max_value=10000000, min_value=0, label=_("Number of visits")) white_active = serializers.BooleanField(required=False, label=_("Whether to enable whitelist")) white_list = serializers.ListSerializer(required=False, child=serializers.CharField(required=True, label=_("Whitelist")), label=_("Whitelist")), show_source = serializers.BooleanField(required=False, label=_("Whether to display knowledge sources")) show_exec = serializers.BooleanField(required=False, label=_("Display execution details")) language = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("language")) authentication = serializers.BooleanField(default=False, label="Do you need authentication") authentication_value = serializers.JSONField(required=False, label="Certified value", default=dict) class AccessTokenSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def edit(self, instance): self.is_valid(raise_exception=True) AccessTokenEditSerializer(data=instance).is_valid(raise_exception=True) application_access_token = QuerySet(ApplicationAccessToken).get( application_id=self.data.get('application_id')) if 'is_active' in instance: application_access_token.is_active = instance.get("is_active") if 'access_token_reset' in instance and instance.get('access_token_reset'): application_access_token.access_token = hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24] if 'access_num' in instance and instance.get('access_num') is not None: application_access_token.access_num = instance.get("access_num") if 'white_active' in instance and instance.get('white_active') is not None: application_access_token.white_active = instance.get("white_active") if 'white_list' in instance and instance.get('white_list') is not None: application_access_token.white_list = instance.get('white_list') if 'show_source' in instance and instance.get('show_source') is not None: application_access_token.show_source = instance.get('show_source') if 'show_exec' in instance and instance.get('show_exec') is not None: application_access_token.show_exec = instance.get('show_exec') if 'language' in instance and instance.get('language') is not None: application_access_token.language = instance.get('language') if 'language' not in instance or instance.get('language') is None: application_access_token.language = None application_access_token.save() license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'), version=Cache_Version.SYSTEM.get_version()) if license_is_valid: if instance.get('authentication') is not None and instance.get( 'authentication_value') is not None: application_access_token.authentication = instance.get('authentication') application_access_token.authentication_value = instance.get('authentication_value') application_access_token.save() return self.one(with_valid=False) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=application_id).first() if application_access_token is None: application_access_token = ApplicationAccessToken(application_id=application_id, access_token=hashlib.md5( str(uuid.uuid7()).encode()).hexdigest()[ 8:24], is_active=True) application_access_token.save() other = {} license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'), version=Cache_Version.SYSTEM.get_version()) if license_is_valid: other = {'authentication': application_access_token.authentication, 'authentication_value': application_access_token.authentication_value} return {'application_id': application_access_token.application_id, 'access_token': application_access_token.access_token, "is_active": application_access_token.is_active, 'access_num': application_access_token.access_num, 'white_active': application_access_token.white_active, 'white_list': application_access_token.white_list, 'show_source': application_access_token.show_source, 'show_exec': application_access_token.show_exec, 'language': application_access_token.language, **other, } ================================================ FILE: apps/application/serializers/application_api_key.py ================================================ import hashlib import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application from application.models.application_api_key import ApplicationApiKey from common.cache_data.application_api_key_cache import get_application_api_key, del_application_api_key from common.db.search import page_search from common.exception.app_exception import AppApiException class ApplicationKeySerializerModel(serializers.ModelSerializer): class Meta: model = ApplicationApiKey fields = "__all__" class EditApplicationKeySerializer(serializers.Serializer): is_active = serializers.BooleanField(required=False, label=_("Availability")) allow_cross_domain = serializers.BooleanField(required=False, label=_("Is cross-domain allowed")) cross_domain_list = serializers.ListSerializer(required=False, child=serializers.CharField(required=True, label=_("Cross-domain address")), label=_("Cross-domain list")) is_permanent = serializers.BooleanField(required=False, label=_("Is permanent")) expire_time = serializers.DateTimeField(required=False, allow_null=True, label=_("Expiration time")) class ApplicationKeySerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_('application id')) order_by = serializers.CharField(required=False, label=_('order by'), allow_null=True, allow_blank=True) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def generate(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") secret_key = 'agent-' + hashlib.md5(str(uuid.uuid7()).encode()).hexdigest() application_api_key = ApplicationApiKey(id=uuid.uuid7(), secret_key=secret_key, application_id=application_id) application_api_key.save() return ApplicationKeySerializerModel(application_api_key).data def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") query_set = QuerySet(ApplicationApiKey).filter(application_id=application_id) order_by = '-create_time' if self.data.get('order_by') is None or self.data.get( 'order_by') == '' else self.data.get('order_by') query_set = query_set.order_by(order_by) return page_search(current_page, page_size, query_set, post_records_handler=lambda u: ApplicationKeySerializerModel(u).data) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_('application id')) api_key_id = serializers.UUIDField(required=True, label=_('ApiKeyId')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) api_key_id = self.data.get("api_key_id") application_id = self.data.get('application_id') application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id, application_id=application_id).first() del_application_api_key(application_api_key.secret_key) application_api_key.delete() def edit(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EditApplicationKeySerializer(data=instance).is_valid(raise_exception=True) api_key_id = self.data.get("api_key_id") application_id = self.data.get('application_id') application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id, application_id=application_id).first() if application_api_key is None: raise AppApiException(500, _('APIKey does not exist')) if 'is_active' in instance and instance.get('is_active') is not None: application_api_key.is_active = instance.get('is_active') if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None: application_api_key.allow_cross_domain = instance.get('allow_cross_domain') if 'cross_domain_list' in instance and instance.get('cross_domain_list') is not None: application_api_key.cross_domain_list = instance.get('cross_domain_list') if 'is_permanent' in instance and instance.get('is_permanent') is not None: application_api_key.is_permanent = instance.get('is_permanent') if not application_api_key.is_permanent: application_api_key.expire_time = instance.get('expire_time') else: application_api_key.expire_time = timezone.now() application_api_key.save() # 写入缓存 get_application_api_key('Bearer ' + application_api_key.secret_key, False) return True ================================================ FILE: apps/application/serializers/application_chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat.py @date:2025/6/10 11:06 @desc: """ import datetime import os import re from io import BytesIO from typing import Dict import openpyxl import pytz from django.core import validators from django.db import models from django.db.models import QuerySet, Q from django.http import StreamingHttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _, gettext from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE from rest_framework import serializers from application.models import Chat, Application, ChatRecord, ChatSourceChoices from common.db.search import get_dynamics_model, native_search, native_page_search, native_page_handler from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR from maxkb.settings import TIME_ZONE, edition class ApplicationChatResponseSerializers(serializers.Serializer): id = serializers.UUIDField(required=True, label=_("chat id")) abstract = serializers.CharField(required=True, label=_("summary")) chat_user_id = serializers.UUIDField(required=True, label=_("Chat User ID")) chat_user_type = serializers.CharField(required=True, label=_("Chat User Type")) is_deleted = serializers.BooleanField(required=True, label=_("Is delete")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) chat_record_count = serializers.IntegerField(required=True, label=_("Number of conversations")) star_num = serializers.IntegerField(required=True, label=_("Number of Likes")) trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs")) mark_sum = serializers.IntegerField(required=True, label=_("Number of tags")) class ApplicationChatRecordExportRequest(serializers.Serializer): select_ids = serializers.ListField(required=True, label=_("Chat ID List"), child=serializers.UUIDField(required=True, label=_("Chat ID"))) class ApplicationChatQuerySerializers(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) abstract = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("summary")) username = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("username")) start_time = serializers.DateField(format='%Y-%m-%d', label=_("Start time")) end_time = serializers.DateField(format='%Y-%m-%d', label=_("End time")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) min_star = serializers.IntegerField(required=False, min_value=0, label=_("Minimum number of likes")) min_trample = serializers.IntegerField(required=False, min_value=0, label=_("Minimum number of clicks")) comparer = serializers.CharField(required=False, label=_("Comparator"), validators=[ validators.RegexValidator(regex=re.compile("^and|or$"), message=_("Only supports and|or"), code=500) ]) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def get_end_time(self): d = datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d').date() naive = datetime.datetime.combine(d, datetime.time.max) return timezone.make_aware(naive, timezone.get_default_timezone()) def get_start_time(self): d = datetime.datetime.strptime(self.data.get('start_time'), '%Y-%m-%d').date() naive = datetime.datetime.combine(d, datetime.time.min) return timezone.make_aware(naive, timezone.get_default_timezone()) def get_query_set(self, select_ids=None): end_time = self.get_end_time() start_time = self.get_start_time() query_set = QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.CharField(), 'application_chat.abstract': models.CharField(), 'application_chat.asker': models.JSONField(), "star_num": models.IntegerField(), 'trample_num': models.IntegerField(), 'comparer': models.CharField(), 'application_chat.update_time': models.DateTimeField(), 'application_chat.id': models.UUIDField(), 'application_chat_record_temp.id': models.UUIDField()})) base_query_dict = {'application_chat.application_id': self.data.get("application_id"), 'application_chat.update_time__gte': start_time, 'application_chat.update_time__lte': end_time, } if 'abstract' in self.data and self.data.get('abstract') is not None: base_query_dict['application_chat.abstract__icontains'] = self.data.get('abstract') if 'username' in self.data and self.data.get('username') is not None: base_query_dict['application_chat.asker__username__icontains'] = self.data.get('username') if select_ids is not None and len(select_ids) > 0: base_query_dict['application_chat.id__in'] = select_ids base_condition = Q(**base_query_dict) min_star_query = None min_trample_query = None if 'min_star' in self.data and self.data.get('min_star') is not None: min_star_query = Q(star_num__gte=self.data.get('min_star')) if 'min_trample' in self.data and self.data.get('min_trample') is not None: min_trample_query = Q(trample_num__gte=self.data.get('min_trample')) if min_star_query is not None and min_trample_query is not None: if self.data.get( 'comparer') is not None and self.data.get('comparer') == 'or': condition = base_condition & (min_star_query | min_trample_query) else: condition = base_condition & (min_star_query & min_trample_query) elif min_star_query is not None: condition = base_condition & min_star_query elif min_trample_query is not None: condition = base_condition & min_trample_query else: condition = base_condition return { 'default_queryset': query_set.filter(condition).order_by("-application_chat.update_time") } def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return native_search(self.get_query_set(), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', ('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__( edition) else 'list_application_chat.sql'))), with_table_name=False) @staticmethod def paragraph_list_to_string(paragraph_list): return "\n**********\n".join( [f"{paragraph.get('title')}:\n{paragraph.get('content')}" for paragraph in paragraph_list] if paragraph_list is not None else '') @staticmethod def to_row(row: Dict): details = row.get('details') or {} padding_problem_text = ' '.join((node.get("answer", "") or "") for key, node in details.items() if node.get("type") == 'question-node') search_dataset_node_list = [(key, node) for key, node in details.items() if node.get("type") == 'search-dataset-node' or node.get( "step_type") == 'search_step' or node.get("type") == 'search-knowledge-node'] reference_paragraph_len = '\n'.join([str(len(node.get('paragraph_list', []))) if key == 'search_step' else node.get( 'name') + ':' + str( len(node.get('paragraph_list', [])) if node.get('paragraph_list', []) is not None else '0') for key, node in search_dataset_node_list]) reference_paragraph = '\n----------\n'.join( [ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list', [])) if key == 'search_step' else node.get( 'name') + ':\n' + ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list', [])) for key, node in search_dataset_node_list]) improve_paragraph_list = row.get('improve_paragraph_list') or [] vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'} vote_reason_map = {'accurate': gettext('accurate'), 'complete': gettext('complete'), 'inaccurate': gettext('inaccurate'), 'incomplete': gettext('incomplete'), 'other': gettext('Other'), } return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text, row.get('answer_text'), vote_status_map.get(row.get('vote_status')), vote_reason_map.get(row.get('vote_reason')), row.get('vote_other_content'), reference_paragraph_len, 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))]), row.get('asker').get('username'), (row.get('message_tokens') or 0) + (row.get('answer_tokens') or 0), row.get('ip_address') or '-', get_source_display(row.get('source')), row.get('run_time'), str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S') if row.get('create_time') is not None else None)] @staticmethod def reset_value(value): if isinstance(value, str): value = re.sub(ILLEGAL_CHARACTERS_RE, '', value) if isinstance(value, datetime.datetime): eastern = pytz.timezone(TIME_ZONE) c = datetime.timezone(eastern._utcoffset) value = value.astimezone(c) return value def export(self, data, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ApplicationChatRecordExportRequest(data=data).is_valid(raise_exception=True) def stream_response(): workbook = openpyxl.Workbook(write_only=True) worksheet = workbook.create_sheet(title='Sheet1') current_page = 1 page_size = 500 headers = [gettext('Conversation ID'), gettext('summary'), gettext('User Questions'), gettext('Problem after optimization'), gettext('answer'), gettext('User feedback'), gettext('Feedback reason'), gettext('Other reason content'), gettext('Reference segment number'), gettext('Section title + content'), gettext('Annotation'), gettext('USER'), gettext('Consuming tokens'), gettext('Ip Address'), gettext('source'), gettext('Time consumed (s)'), gettext('Question Time')] worksheet.append(headers) for data_list in native_page_handler(page_size, self.get_query_set(data.get('select_ids')), primary_key='application_chat_record_temp.id', primary_queryset='default_queryset', get_primary_value=lambda item: item.get('id'), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', ('export_application_chat_ee.sql' if ['PE', 'EE'].__contains__( edition) else 'export_application_chat.sql'))), with_table_name=False): for item in data_list: row = [self.reset_value(v) for v in self.to_row(item)] worksheet.append(row) current_page = current_page + 1 output = BytesIO() workbook.save(output) output.seek(0) yield output.getvalue() output.close() workbook.close() response = StreamingHttpResponse(stream_response(), content_type='application/vnd.open.xmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename="data.xlsx"' return response def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return native_page_search(current_page, page_size, self.get_query_set(), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', ('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__( edition) else 'list_application_chat.sql'))), with_table_name=False) class ChatCountSerializer(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) def get_query_set(self): return QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')) def update_chat(self): self.is_valid(raise_exception=True) count_chat_record = native_search(self.get_query_set(), get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'count_chat_record.sql')), with_search_one=True) QuerySet(Chat).filter(id=self.data.get('chat_id')).update(star_num=count_chat_record.get('star_num', 0) or 0, trample_num=count_chat_record.get('trample_num', 0) or 0, chat_record_count=count_chat_record.get( 'chat_record_count', 0) or 0, mark_sum=count_chat_record.get('mark_sum', 0) or 0) return True def get_source_display(source): if not source or not isinstance(source, dict) or 'type' not in source: return '-' source_type = source.get('type') # 定义映射关系 source_mapping = { ChatSourceChoices.ONLINE.value: gettext('Online Usage'), ChatSourceChoices.API_CALL.value: gettext('API Call'), ChatSourceChoices.ENTERPRISE_WECHAT.value: gettext('Enterprise WeChat'), ChatSourceChoices.WECHAT_PUBLIC_ACCOUNT.value: gettext('WeChat Public Account'), ChatSourceChoices.LARK.value: gettext('Lark'), ChatSourceChoices.DINGTALK.value: gettext('DingTalk'), ChatSourceChoices.ENTERPRISE_WECHAT_ROBOT.value: gettext('Enterprise WeChat Robot'), ChatSourceChoices.TRIGGER.value: gettext('Trigger'), ChatSourceChoices.SLACK.value: gettext('Slack'), } return source_mapping.get(source_type, str(source_type)) ================================================ FILE: apps/application/serializers/application_chat_link.py ================================================ """ @project: MaxKB @Author: niu @file: application_chat_link.py @date: 2026/2/9 10:50 @desc: """ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Chat, ChatShareLink, ShareLinkType, ChatRecord from common.exception.app_exception import AppApiException from common.utils.chat_link_code import UUIDEncoder import uuid_utils.compat as uuid class ShareChatRecordModelSerializer(serializers.ModelSerializer): class Meta: model = ChatRecord fields = ['id', 'problem_text', 'answer_text', 'answer_text_list', 'create_time'] class ChatRecordShareLinkRequestSerializer(serializers.Serializer): chat_record_ids = serializers.ListSerializer( child=serializers.UUIDField(), required=False, allow_empty=False, label=_("Chat record IDs") ) is_current_all = serializers.BooleanField(required=False, default=False) def validate(self, attrs): if not attrs.get('is_current_all') and not attrs.get('chat_record_ids'): raise serializers.ValidationError(_('Chat record ids can not be empty')) return attrs class ChatRecordShareLinkSerializer(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) user_id = serializers.UUIDField(required=False, label=_("User ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) chat_id = self.data.get('chat_id') application_id = self.data.get('application_id') chat_query_set = Chat.objects.filter(id=chat_id, application_id=application_id, is_deleted=False) if not chat_query_set.exists(): raise AppApiException(500, _('Chat id does not exist')) def generate_link(self, instance, with_valid=True): if with_valid: request_serializer = ChatRecordShareLinkRequestSerializer(data=instance) request_serializer.is_valid(raise_exception=True) self.is_valid(raise_exception=True) if not instance.get('is_current_all', False): chat_record_ids: list[str] = instance.get('chat_record_ids') record_count = ChatRecord.objects.filter(id__in=chat_record_ids, chat_id=self.data.get('chat_id')).count() if record_count != len(chat_record_ids): raise AppApiException(500, _('Invalid chat record ids')) chat_id = self.data.get('chat_id') application_id = self.data.get('application_id') user_id = self.data.get('user_id') is_current_all = instance.get('is_current_all', False) if is_current_all: sorted_ids = list( ChatRecord.objects.filter(chat_id=chat_id).order_by('create_time').values_list('id',flat=True) ) else: chat_record_ids: list[str] = instance.get('chat_record_ids') sorted_ids = list(ChatRecord.objects.filter(id__in=chat_record_ids).order_by('create_time').values_list('id',flat=True)) existing = ChatShareLink.objects.filter( chat_id=chat_id, application_id=application_id, share_type=ShareLinkType.PUBLIC, user_id=user_id, chat_record_ids=sorted_ids ).first() if existing: return {'link': UUIDEncoder.encode(existing.id)} chat_share_link_model = ChatShareLink( id=uuid.uuid7(), chat_id=chat_id, application_id=application_id, share_type=ShareLinkType.PUBLIC, user_id=user_id, chat_record_ids=sorted_ids ) chat_share_link_model.save() link = UUIDEncoder.encode(chat_share_link_model.id) return {'link': link} class ChatShareLinkDetailSerializer(serializers.Serializer): link = serializers.CharField(required=True, label=_("Link")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) link = self.data.get('link') share_link_id = UUIDEncoder.decode_to_str(link) share_link_query_set = ChatShareLink.objects.filter(id=share_link_id).first() if not share_link_query_set: raise AppApiException(500, _('Share link does not exist')) if share_link_query_set.chat.is_deleted: raise AppApiException(500, _('Chat has been deleted')) return share_link_query_set def get_record_list(self): share_link_model = self.is_valid(raise_exception=True) chat_record_model_list = ChatRecord.objects.filter(id__in=share_link_model.chat_record_ids, chat_id=share_link_model.chat_id).order_by('create_time') abstract = Chat.objects.filter( id=share_link_model.chat_id ).values_list('abstract', flat=True).first() chat_record_list = ShareChatRecordModelSerializer(chat_record_model_list, many=True).data return { 'abstract': abstract, 'chat_record_list': chat_record_list } ================================================ FILE: apps/application/serializers/application_chat_record.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat_record.py @date:2025/6/10 15:10 @desc: """ from functools import reduce from typing import Dict import uuid_utils.compat as uuid from django.db import transaction from django.db.models import QuerySet from django.db.models.aggregates import Max, Min from django.utils.translation import gettext_lazy as _, gettext from rest_framework import serializers from rest_framework.utils.formatting import lazy_format from application.models import ChatRecord, ApplicationAccessToken, Application from application.serializers.application_chat import ChatCountSerializer from application.serializers.common import ChatInfo from common.auth.authentication import get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.db.search import page_search from common.exception.app_exception import AppApiException, AppUnauthorizedFailed from common.utils.common import post from knowledge.models import Paragraph, Document, Problem, ProblemParagraphMapping, Knowledge from knowledge.serializers.common import get_embedding_model_id_by_knowledge_id, update_document_char_length from knowledge.serializers.paragraph import ParagraphSerializers from knowledge.task.embedding import embedding_by_paragraph, embedding_by_paragraph_list class ChatRecordSerializerModel(serializers.ModelSerializer): class Meta: model = ChatRecord fields = ['id', 'chat_id', 'vote_status','vote_reason','vote_other_content', 'problem_text', 'answer_text', 'message_tokens', 'answer_tokens', 'const', 'improve_paragraph_id_list', 'run_time', 'index', 'answer_text_list', 'create_time', 'update_time'] class ChatRecordOperateSerializer(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id")) def is_valid(self, *, debug=False, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=self.data.get('application_id')).first() if application_access_token is None: raise AppApiException(500, gettext('Application authentication information does not exist')) def get_chat_record(self): chat_record_id = self.data.get('chat_record_id') chat_id = self.data.get('chat_id') chat_info: ChatInfo = ChatInfo.get_cache(chat_id) if chat_info is not None: chat_record_list = [chat_record for chat_record in chat_info.chat_record_list if str(chat_record.id) == str(chat_record_id)] if chat_record_list is not None and len(chat_record_list): return chat_record_list[-1] return QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() def one(self, debug): self.is_valid(debug=debug, raise_exception=True) chat_record = self.get_chat_record() if chat_record is None: raise AppApiException(500, gettext("Conversation does not exist")) application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=self.data.get('application_id')).first() show_source = False show_exec = False if application_access_token is not None: show_exec = application_access_token.show_exec show_source = application_access_token.show_source return ApplicationChatRecordQuerySerializers.reset_chat_record( chat_record, True if debug else show_source, True if debug else show_exec) class ApplicationChatRecordQuerySerializers(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) chat_id = serializers.UUIDField(required=True, label=_("Chat ID")) order_asc = serializers.BooleanField(required=False, allow_null=True, label=_("Is it in order")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')) order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get( 'order_asc') else '-create_time' return [ChatRecordSerializerModel(chat_record).data for chat_record in QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by)] @staticmethod def get_loop_workflow_node(details): result = [] for item in details.values(): if item.get('type') == 'loop-node': for loop_item in item.get('loop_node_data') or []: for inner_item in loop_item.values(): result.append(inner_item) return result @staticmethod def reset_chat_record(chat_record, show_source, show_exec): knowledge_list = [] paragraph_list = [] if 'search_step' in chat_record.details and chat_record.details.get('search_step').get( 'paragraph_list') is not None: paragraph_list = chat_record.details.get('search_step').get( 'paragraph_list') for item in [*chat_record.details.values(), *ApplicationChatRecordQuerySerializers.get_loop_workflow_node(chat_record.details)]: if item.get('type') == 'search-knowledge-node' and item.get('show_knowledge', False): paragraph_list = paragraph_list + (item.get( 'paragraph_list') or []) if item.get('type') == 'reranker-node' and item.get('show_knowledge', False): paragraph_list = paragraph_list + [rl.get('metadata') for rl in (item.get('result_list') or []) if 'document_id' in (rl.get('metadata') or {}) and 'knowledge_id' in ( rl.get( 'metadata') or {})] paragraph_list = list({p.get('id'): p for p in paragraph_list}.values()) knowledge_list = knowledge_list + [{'id': knowledge_id, **knowledge} for knowledge_id, knowledge in reduce(lambda x, y: {**x, **y}, [{row.get( 'knowledge_id'): {'knowledge_name': row.get( "knowledge_name"), 'knowledge_type': row.get('knowledge_type')}} for row in paragraph_list], {}).items()] if len(chat_record.improve_paragraph_id_list) > 0: paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list) if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list): paragraph_model_id_list = [str(p.id) for p in paragraph_model_list] chat_record.improve_paragraph_id_list = list( filter(lambda p_id: paragraph_model_id_list.__contains__(p_id), chat_record.improve_paragraph_id_list)) chat_record.save() show_source_dict = {'knowledge_list': knowledge_list, 'paragraph_list': paragraph_list, } show_exec_dict = {'execution_details': [chat_record.details[key] for key in chat_record.details if (True if show_exec else chat_record.details[key].get( 'type') == 'start-node')]} return { **ChatRecordSerializerModel(chat_record).data, 'padding_problem_text': chat_record.details.get('problem_padding').get( 'padding_problem_text') if 'problem_padding' in chat_record.details else None, **(show_source_dict if show_source else {}), **(show_exec_dict if show_exec else show_exec_dict) } def page(self, current_page: int, page_size: int, with_valid=True, show_source=None, show_exec=None): if with_valid: self.is_valid(raise_exception=True) order_by = '-create_time' if self.data.get('order_asc') is None or self.data.get( 'order_asc') else 'create_time' if show_source is None: show_source = True if show_exec is None: show_exec = True page = page_search(current_page, page_size, QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by), post_records_handler=lambda chat_record: self.reset_chat_record(chat_record, show_source, show_exec)) return page class ParagraphModel(serializers.ModelSerializer): class Meta: model = Paragraph fields = "__all__" class ChatRecordImproveSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def get(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) chat_record_id = self.data.get('chat_record_id') chat_id = self.data.get('chat_id') chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() if chat_record is None: raise AppApiException(500, gettext('Conversation record does not exist')) if chat_record.improve_paragraph_id_list is None or len(chat_record.improve_paragraph_id_list) == 0: return [] paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list) if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list): paragraph_model_id_list = [str(p.id) for p in paragraph_model_list] chat_record.improve_paragraph_id_list = list( filter(lambda p_id: paragraph_model_id_list.__contains__(p_id), chat_record.improve_paragraph_id_list)) chat_record.save() return [ParagraphModel(p).data for p in paragraph_model_list] class ApplicationChatRecordImproveInstanceSerializer(serializers.Serializer): title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True, label=_("Section title")) content = serializers.CharField(required=True, label=_("Paragraph content")) problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True, label=_("question")) class ApplicationChatRecordAddKnowledgeSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id")) document_id = serializers.UUIDField(required=True, label=_("Document id")) chat_ids = serializers.ListSerializer(child=serializers.UUIDField(), required=True, label=_("Conversation ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) if not Document.objects.filter(id=self.data['document_id'], knowledge_id=self.data['knowledge_id']).exists(): raise AppApiException(500, gettext("The document id is incorrect")) @staticmethod def post_embedding_paragraph(paragraph_ids, knowledge_id): model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) embedding_by_paragraph_list(paragraph_ids, model_id) @post(post_function=post_embedding_paragraph) @transaction.atomic def post_improve(self, instance: Dict, request=None, scope='WORKSPACE', with_valid=True): if with_valid: ApplicationChatRecordAddKnowledgeSerializer(data=instance).is_valid(raise_exception=True) self.is_valid(raise_exception=True) if scope == 'WORKSPACE': is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) else: is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN ) if not is_permission: raise AppUnauthorizedFailed(403, gettext('No permission to access')) chat_ids = instance['chat_ids'] document_id = instance['document_id'] knowledge_id = instance['knowledge_id'] # 获取所有聊天记录 chat_record_list = list(ChatRecord.objects.filter(chat_id__in=chat_ids)) if len(chat_record_list) < len(chat_ids): raise AppApiException(500, gettext("Conversation records that do not exist")) # 批量创建段落和问题映射 paragraphs = [] paragraph_ids = [] problem_paragraph_mappings = [] for chat_record in chat_record_list: paragraph = Paragraph( id=uuid.uuid7(), document_id=document_id, content=chat_record.answer_text, knowledge_id=knowledge_id, title=chat_record.problem_text ) problem, _ = Problem.objects.get_or_create(content=chat_record.problem_text, knowledge_id=knowledge_id) problem_paragraph_mapping = ProblemParagraphMapping( id=uuid.uuid7(), knowledge_id=knowledge_id, document_id=document_id, problem_id=problem.id, paragraph_id=paragraph.id ) paragraphs.append(paragraph) paragraph_ids.append(paragraph.id) problem_paragraph_mappings.append(problem_paragraph_mapping) chat_record.improve_paragraph_id_list.append(paragraph.id) # 处理段落位置 self.prepend_paragraphs(document_id, paragraphs) # 批量创建新段落和问题映射 Paragraph.objects.bulk_create(paragraphs) ProblemParagraphMapping.objects.bulk_create(problem_paragraph_mappings) # 批量保存聊天记录 ChatRecord.objects.bulk_update(chat_record_list, ['improve_paragraph_id_list']) update_document_char_length(document_id) for chat_id in chat_ids: ChatCountSerializer(data={'chat_id': chat_id}).update_chat() return paragraph_ids, knowledge_id @staticmethod def prepend_paragraphs(document_id, paragraphs): # 获取所有现有段落 existing_paragraphs = list(Paragraph.objects.filter( document_id=document_id ).order_by('position')) # 计算新段落数量 new_count = len(paragraphs) # 如果已有段落,需要重新调整所有段落的位置 if existing_paragraphs: # 为现有段落重新分配位置,从新段落数量+1开始 for i, existing_paragraph in enumerate(existing_paragraphs): existing_paragraph.position = new_count + i + 1 # 批量更新现有段落位置 if existing_paragraphs: Paragraph.objects.bulk_update(existing_paragraphs, ['position']) # 为新段落分配位置,从1开始 for i, paragraph in enumerate(paragraphs): paragraph.position = i + 1 class ApplicationChatRecordImproveSerializer(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id")) knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id")) document_id = serializers.UUIDField(required=True, label=_("Document id")) application_id = serializers.UUIDField(required=True, label=_("Application id")) workspace_id = serializers.CharField(required=True, label=_("Workspace ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Document).filter(id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id')).exists(): raise AppApiException(500, gettext("The document id is incorrect")) @staticmethod def post_embedding_paragraph(chat_record, paragraph_id, knowledge_id): model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) # 发送向量化事件 embedding_by_paragraph(paragraph_id, model_id) return chat_record @post(post_function=post_embedding_paragraph) @transaction.atomic def improve(self, instance: Dict, request=None, scope='WORKSPACE', with_valid=True): if with_valid: self.is_valid(raise_exception=True) if scope == 'WORKSPACE': is_permission = get_is_permissions(request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) else: is_permission = get_is_permissions(request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN ) if not is_permission: raise AppUnauthorizedFailed(403, gettext('No permission to access')) ApplicationChatRecordImproveInstanceSerializer(data=instance).is_valid(raise_exception=True) chat_record_id = self.data.get('chat_record_id') chat_id = self.data.get('chat_id') chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() if chat_record is None: raise AppApiException(500, gettext('Conversation record does not exist')) document_id = self.data.get("document_id") knowledge_id = self.data.get("knowledge_id") max_position = Paragraph.objects.filter(document_id=document_id).aggregate( max_position=Max('position') )['max_position'] or 0 paragraph = Paragraph( id=uuid.uuid7(), document_id=document_id, content=instance.get("content"), knowledge_id=knowledge_id, title=instance.get("title") if 'title' in instance else '', position=max_position + 1 ) problem_text = instance.get('problem_text') if instance.get( 'problem_text') is not None else chat_record.problem_text problem, _ = QuerySet(Problem).get_or_create(content=problem_text, knowledge_id=knowledge_id) problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid7(), knowledge_id=knowledge_id, document_id=document_id, problem_id=problem.id, paragraph_id=paragraph.id) # 插入段落 paragraph.save() # 插入关联问题 problem_paragraph_mapping.save() chat_record.improve_paragraph_id_list.append(paragraph.id) update_document_char_length(document_id) # 添加标注 chat_record.save() ChatCountSerializer(data={'chat_id': chat_id}).update_chat() return ChatRecordSerializerModel(chat_record).data, paragraph.id, knowledge_id class Operate(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id")) knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id")) document_id = serializers.UUIDField(required=True, label=_("Document id")) paragraph_id = serializers.UUIDField(required=True, label=_("Paragraph id")) workspace_id = serializers.CharField(required=True, label=_("Workspace ID")) def delete(self, request=None, scope='WORKSPACE', with_valid=True): if with_valid: self.is_valid(raise_exception=True) if scope == 'WORKSPACE': is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) else: is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'), knowledge_id=self.data.get("knowledge_id"))( PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN ) if not is_permission: raise AppUnauthorizedFailed(403, gettext('No permission to access')) workspace_id = self.data.get('workspace_id') chat_record_id = self.data.get('chat_record_id') chat_id = self.data.get('chat_id') knowledge_id = self.data.get('knowledge_id') document_id = self.data.get('document_id') paragraph_id = self.data.get('paragraph_id') chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() if chat_record is None: raise AppApiException(500, gettext('Conversation record does not exist')) if not chat_record.improve_paragraph_id_list.__contains__(uuid.UUID(paragraph_id)): message = lazy_format( gettext( 'The paragraph id is wrong. The current conversation record does not exist. [{paragraph_id}] paragraph id'), paragraph_id=paragraph_id) raise AppApiException(500, message.__str__()) chat_record.improve_paragraph_id_list = [row for row in chat_record.improve_paragraph_id_list if str(row) != paragraph_id] chat_record.save() o = ParagraphSerializers.Operate( data={"workspace_id": workspace_id, "knowledge_id": knowledge_id, 'document_id': document_id, "paragraph_id": paragraph_id}) o.is_valid(raise_exception=True) o.delete() return True ================================================ FILE: apps/application/serializers/application_folder.py ================================================ from rest_framework import serializers from application.models import ApplicationFolder class ApplicationFolderTreeSerializer(serializers.ModelSerializer): children = serializers.SerializerMethodField() class Meta: model = ApplicationFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children','create_time','update_time'] def get_children(self, obj): return ApplicationFolderTreeSerializer(obj.get_children(), many=True).data class ApplicationFolderFlatSerializer(serializers.ModelSerializer): class Meta: model = ApplicationFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id'] ================================================ FILE: apps/application/serializers/application_stats.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_stats.py @date:2025/6/9 20:34 @desc: """ import datetime import os from typing import Dict, List from django.db import models from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from django.utils import timezone from rest_framework import serializers from application.models import ApplicationChatUserStats, Application from common.db.search import native_search, get_dynamics_model from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR from maxkb.settings import edition class ApplicationStatsSerializer(serializers.Serializer): chat_record_count = serializers.IntegerField(required=True, label=_("Number of conversations")) customer_added_count = serializers.IntegerField(required=True, label=_("Number of new users")) customer_num = serializers.IntegerField(required=True, label=_("Total number of users")) day = serializers.CharField(required=True, label=_("date")) star_num = serializers.IntegerField(required=True, label=_("Number of Likes")) tokens_num = serializers.IntegerField(required=True, label=_("Tokens consumption")) trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs")) class ApplicationStatisticsSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) start_time = serializers.DateField(format='%Y-%m-%d', label=_("Start time")) end_time = serializers.DateField(format='%Y-%m-%d', label=_("End time")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def get_end_time(self): d = datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d').date() naive = datetime.datetime.combine(d, datetime.time.max) return timezone.make_aware(naive, timezone.get_default_timezone()) def get_start_time(self): d = datetime.datetime.strptime(self.data.get('start_time'), '%Y-%m-%d').date() naive = datetime.datetime.combine(d, datetime.time.min) return timezone.make_aware(naive, timezone.get_default_timezone()) def get_customer_count_trend(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) start_time = self.get_start_time() end_time = self.get_end_time() return native_search( {'default_sql': QuerySet(ApplicationChatUserStats).filter( application_id=self.data.get('application_id'), create_time__gte=start_time, create_time__lte=end_time)}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count_trend.sql'))) def get_chat_record_aggregate_trend(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) start_time = self.get_start_time() end_time = self.get_end_time() chat_record_aggregate_trend = native_search( {'default_sql': QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.UUIDField(), 'application_chat_record.create_time': models.DateTimeField()})).filter( **{'application_chat.application_id': self.data.get('application_id'), 'application_chat_record.create_time__gte': start_time, 'application_chat_record.create_time__lte': end_time} )}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count_trend.sql'))) customer_count_trend = self.get_customer_count_trend(with_valid=False) return self.merge_customer_chat_record(chat_record_aggregate_trend, customer_count_trend) def merge_customer_chat_record(self, chat_record_aggregate_trend: List[Dict], customer_count_trend: List[Dict]): return [{**self.find(chat_record_aggregate_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, {'star_num': 0, 'trample_num': 0, 'tokens_num': 0, 'chat_record_count': 0, 'customer_num': 0, 'day': day}), **self.find(customer_count_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, {'customer_added_count': 0})} for day in self.get_days_between_dates(self.data.get('start_time'), self.data.get('end_time'))] @staticmethod def find(source_list, condition, default): value_list = [row for row in source_list if condition(row)] if len(value_list) > 0: return value_list[0] return default @staticmethod def get_days_between_dates(start_date, end_date): start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d') days = [] current_date = start_date while current_date <= end_date: days.append(current_date.strftime('%Y-%m-%d')) current_date += datetime.timedelta(days=1) return days def get_token_usage_statistics(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) start_time = self.get_start_time() end_time = self.get_end_time() get_token_usage = native_search( {'default_sql': QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.UUIDField(), 'application_chat_record.create_time': models.DateTimeField()})).filter( **{'application_chat.application_id': self.data.get('application_id'), 'application_chat_record.create_time__gte': start_time, 'application_chat_record.create_time__lte': end_time} )}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', ('get_token_usage_ee.sql' if ['PE', 'EE'].__contains__( edition) else 'get_token_usage.sql')))) return get_token_usage def get_top_questions_statistics(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) start_time = self.get_start_time() end_time = self.get_end_time() get_top_questions = native_search( {'default_sql': QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.UUIDField(), 'application_chat_record.create_time': models.DateTimeField()})).filter( **{'application_chat.application_id': self.data.get('application_id'), 'application_chat_record.create_time__gte': start_time, 'application_chat_record.create_time__lte': end_time} )}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', ( 'top_questions_ee.sql' if ['PE', 'EE'].__contains__(edition) else 'top_questions.sql')))) return get_top_questions ================================================ FILE: apps/application/serializers/application_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py @date:2025/6/3 16:25 @desc: """ from typing import Dict from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application, ApplicationVersion from common.db.search import page_search from common.exception.app_exception import AppApiException class ApplicationVersionQuerySerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("summary")) class ApplicationVersionModelSerializer(serializers.ModelSerializer): class Meta: model = ApplicationVersion fields = ['id', 'name', 'workspace_id', 'application_id', 'work_flow', 'publish_user_id', 'publish_user_name', 'create_time', 'update_time'] class ApplicationVersionEditSerializer(serializers.Serializer): name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True, label=_("Version Name")) class ApplicationVersionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_("Workspace ID")) class Query(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def get_query_set(self, query): query_set = QuerySet(ApplicationVersion).filter(application_id=query.get('application_id')) if 'name' in query and query.get('name') is not None: query_set = query_set.filter(name__contains=query.get('name')) if 'workspace_id' in self.data and self.data.get('workspace_id') is not None: query_set = query_set.filter(workspace_id=self.data.get('workspace_id')) return query_set.order_by("-create_time") def list(self, query, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ApplicationVersionQuerySerializer(data=query).is_valid(raise_exception=True) query_set = self.get_query_set(query) return [ApplicationVersionModelSerializer(v).data for v in query_set] def page(self, query, current_page, page_size, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return page_search(current_page, page_size, self.get_query_set(query), post_records_handler=lambda v: ApplicationVersionModelSerializer(v).data) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) application_version_id = serializers.UUIDField(required=True, label=_("Application version ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Application id does not exist')) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'), id=self.data.get( 'application_version_id')).first() if application_version is not None: return ApplicationVersionModelSerializer(application_version).data else: raise AppApiException(500, _('Workflow version does not exist')) def edit(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ApplicationVersionEditSerializer(data=instance).is_valid(raise_exception=True) application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'), id=self.data.get( 'application_version_id')).first() if application_version is not None: name = instance.get('name', None) if name is not None and len(name) > 0: application_version.name = name application_version.save() return ApplicationVersionModelSerializer(application_version).data else: raise AppApiException(500, _('Workflow version does not exist')) ================================================ FILE: apps/application/serializers/common.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: common.py @date:2025/6/9 13:42 @desc: """ from typing import List from django.core.cache import cache from django.db.models import QuerySet from django.utils import timezone from django.utils.translation import gettext_lazy as _ from application.models import Application, ChatRecord, Chat, ApplicationVersion, ChatUserType, ApplicationTypeChoices from application.serializers.application_chat import ChatCountSerializer from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import ChatException from knowledge.models import Document from models_provider.models import Model from models_provider.tools import get_model_credential from system_manage.models.resource_mapping import ResourceMapping from tools.models import ToolRecord class ToolExecute: def __init__(self, tool_id: str, tool_record_id: str, workspace_id: str, source_type, source_id, debug=False): self.tool_id = tool_id self.workspace_id = workspace_id self.source_type = source_type self.source_id = source_id self.tool_record_id = tool_record_id self.debug = debug def get_record(self): if self.tool_record_id: if self.debug: return self.to_record(cache.get(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id), version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version())) else: return QuerySet(ToolRecord).filter(tool_id=self.tool_id, id=self.tool_record_id).first() return None def to_record(self, tool_record_dict): if tool_record_dict is None: return None return ToolRecord(id=tool_record_dict.get('id'), tool_id=tool_record_dict.get('tool_id'), workspace_id=tool_record_dict.get('workspace_id'), source_type=tool_record_dict.get('source_type'), source_id=tool_record_dict.get('source_id'), meta=tool_record_dict.get('meta'), state=tool_record_dict.get('state'), run_time=tool_record_dict.get('run_time')) def to_dict(self, tool_record): return {'id': tool_record.id, 'tool_id': tool_record.tool_id, 'workspace_id': tool_record.workspace_id, 'source_type': tool_record.source_type, 'source_id': tool_record.source_id, 'meta': tool_record.meta, 'state': tool_record.state, 'run_time': tool_record.run_time} def set_record(self, tool_record): cache.set(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id), self.to_dict(tool_record), version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version(), timeout=60 * 30) if not self.debug: QuerySet(ToolRecord).update_or_create(id=tool_record.id, create_defaults={'id': tool_record.id, 'tool_id': tool_record.tool_id, 'workspace_id': tool_record.workspace_id, "source_type": tool_record.source_type, 'source_id': tool_record.source_id, 'meta': tool_record.meta, 'run_time': tool_record.run_time}, defaults={ 'workspace_id': tool_record.workspace_id, 'tool_id': tool_record.tool_id, "source_type": tool_record.source_type, 'source_id': tool_record.source_id, 'meta': tool_record.meta, 'run_time': tool_record.run_time }) class ChatInfo: def __init__(self, chat_id: str, chat_user_id: str, chat_user_type: str, ip_address: str, source: {}, knowledge_id_list: List[str], exclude_document_id_list: list[str], application_id: str, debug=False): """ :param chat_id: 对话id :param chat_user_id 对话用户id :param chat_user_type 对话用户类型 :param knowledge_id_list: 知识库列表 :param exclude_document_id_list: 排除的文档 :param application_id 应用id :param debug 是否是调试 :param ip_address: 用户ip地址 :param source: 用户来源 """ self.chat_id = chat_id self.chat_user_id = chat_user_id self.chat_user_type = chat_user_type self.knowledge_id_list = knowledge_id_list self.exclude_document_id_list = exclude_document_id_list self.application_id = application_id self.chat_record_list: List[ChatRecord] = [] self.application = None self.chat_user = None self.ip_address = ip_address self.source = source self.debug = debug @staticmethod def get_no_references_setting(knowledge_setting, model_setting): no_references_setting = knowledge_setting.get( 'no_references_setting', { 'status': 'ai_questioning', 'value': '{question}'}) if no_references_setting.get('status') == 'ai_questioning': no_references_prompt = model_setting.get('no_references_prompt', '{question}') no_references_setting['value'] = no_references_prompt if len(no_references_prompt) > 0 else "{question}" return no_references_setting def get_application(self): if self.debug: application = QuerySet(Application).filter(id=self.application_id).first() if not application: raise ChatException(500, _('The application does not exist')) else: application = QuerySet(ApplicationVersion).filter(application_id=self.application_id).order_by( '-create_time')[0:1].first() if not application: raise ChatException(500, _("The application has not been published. Please use it after publishing.")) if application.type == ApplicationTypeChoices.SIMPLE.value: # 数据集id列表 knowledge_id_list = [str(row.target_id) for row in QuerySet(ResourceMapping).filter(source_id=self.application_id, source_type='APPLICATION', target_type='KNOWLEDGE')] # 需要排除的文档 exclude_document_id_list = [str(document.id) for document in QuerySet(Document).filter( knowledge_id__in=knowledge_id_list, is_active=False)] self.knowledge_id_list = knowledge_id_list self.exclude_document_id_list = exclude_document_id_list self.application = application return application def get_chat_user(self, asker=None): if self.chat_user: return self.chat_user chat_user_model = DatabaseModelManage.get_model("chat_user") if self.chat_user_type == ChatUserType.CHAT_USER.value and chat_user_model: chat_user = QuerySet(chat_user_model).filter(id=self.chat_user_id).first() return { 'id': str(chat_user.id), 'email': chat_user.email, 'phone': chat_user.phone, 'nick_name': chat_user.nick_name, 'username': chat_user.username, 'source': chat_user.source } else: if asker: if isinstance(asker, dict): self.chat_user = asker else: self.chat_user = {'username': asker} else: self.chat_user = {'username': '游客'} return self.chat_user def get_chat_user_group(self, asker=None): chat_user = self.get_chat_user(asker=asker) chat_user_id = chat_user.get('id') if not chat_user_id: return [] user_group_relation_model = DatabaseModelManage.get_model("user_group_relation") if user_group_relation_model: return [{ 'id': user_group_relation.group_id, 'name': user_group_relation.group.name } for user_group_relation in QuerySet(user_group_relation_model).select_related('group').filter(user_id=chat_user_id)] return [] def to_base_pipeline_manage_params(self): self.get_application() self.get_chat_user() knowledge_setting = self.application.knowledge_setting model_setting = self.application.model_setting model_id = self.application.model_id model_params_setting = None if model_id is not None: model = QuerySet(Model).filter(id=model_id).first() credential = get_model_credential(model.provider, model.model_type, model.model_name) model_params_setting = credential.get_model_params_setting_form(model.model_name).get_default_form_data() return { 'knowledge_id_list': self.knowledge_id_list, 'exclude_document_id_list': self.exclude_document_id_list, 'exclude_paragraph_id_list': [], 'top_n': 3 if knowledge_setting.get('top_n') is None else knowledge_setting.get('top_n'), 'similarity': 0.6 if knowledge_setting.get('similarity') is None else knowledge_setting.get('similarity'), 'max_paragraph_char_number': knowledge_setting.get('max_paragraph_char_number') or 5000, 'history_chat_record': self.chat_record_list, 'chat_id': self.chat_id, 'dialogue_number': self.application.dialogue_number, 'problem_optimization_prompt': self.application.problem_optimization_prompt if self.application.problem_optimization_prompt is not None and len( self.application.problem_optimization_prompt) > 0 else _( "() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the tag"), 'prompt': model_setting.get( 'prompt') if 'prompt' in model_setting and len(model_setting.get( 'prompt')) > 0 else Application.get_default_model_prompt(), 'system': model_setting.get( 'system', None), 'model_id': model_id, 'problem_optimization': self.application.problem_optimization, 'stream': True, 'model_setting': model_setting, 'model_params_setting': model_params_setting if self.application.model_params_setting is None or len( self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting, 'search_mode': self.application.knowledge_setting.get('search_mode') or 'embedding', 'no_references_setting': self.get_no_references_setting(self.application.knowledge_setting, model_setting), 'workspace_id': self.application.workspace_id, 'application_id': self.application_id, 'mcp_enable': self.application.mcp_enable, 'mcp_tool_ids': self.application.mcp_tool_ids, 'mcp_servers': self.application.mcp_servers, 'mcp_source': self.application.mcp_source, 'tool_enable': self.application.tool_enable, 'tool_ids': self.application.tool_ids, 'application_enable': self.application.application_enable, 'application_ids': self.application.application_ids, 'skill_tool_ids': self.application.skill_tool_ids, 'mcp_output_enable': self.application.mcp_output_enable, } def to_pipeline_manage_params(self, problem_text: str, post_response_handler, exclude_paragraph_id_list, chat_user_id: str, chat_user_type, ip_address, source, stream=True, form_data=None): if form_data is None: form_data = {} params = self.to_base_pipeline_manage_params() return {**params, 'problem_text': problem_text, 'post_response_handler': post_response_handler, 'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'ip_address': ip_address, 'source': source, 'form_data': form_data} def set_chat(self, question): if not self.debug: if not QuerySet(Chat).filter(id=self.chat_id).exists(): Chat(id=self.chat_id, application_id=self.application_id, abstract=question[0:1024], chat_user_id=self.chat_user_id, chat_user_type=self.chat_user_type, ip_address=self.ip_address, source=self.source, asker=self.get_chat_user()).save() def set_chat_variable(self, chat_context): if not self.debug: chat = QuerySet(Chat).filter(id=self.chat_id).first() if chat: chat.meta = {**(chat.meta if isinstance(chat.meta, dict) else {}), **chat_context} chat.save() else: cache.set(Cache_Version.CHAT_VARIABLE.get_key(key=self.chat_id), chat_context, version=Cache_Version.CHAT_VARIABLE.get_version(), timeout=60 * 30) def get_chat_variable(self): if not self.debug: chat = QuerySet(Chat).filter(id=self.chat_id).first() if chat: return chat.meta return {} else: return cache.get(Cache_Version.CHAT_VARIABLE.get_key(key=self.chat_id), version=Cache_Version.CHAT_VARIABLE.get_version()) or {} def append_chat_record(self, chat_record: ChatRecord): chat_record.problem_text = chat_record.problem_text[0:10240] if chat_record.problem_text is not None else "" chat_record.answer_text = chat_record.answer_text[0:40960] if chat_record.problem_text is not None else "" is_save = True # 存入缓存中 for index in range(len(self.chat_record_list)): record = self.chat_record_list[index] if record.id == chat_record.id: self.chat_record_list[index] = chat_record is_save = False break if is_save: self.chat_record_list.append(chat_record) if not self.debug: if not QuerySet(Chat).filter(id=self.chat_id).exists(): Chat(id=self.chat_id, application_id=self.application_id, abstract=chat_record.problem_text[0:1024], chat_user_id=self.chat_user_id, chat_user_type=self.chat_user_type, ip_address=self.ip_address, source=self.source, asker=self.get_chat_user()).save() else: QuerySet(Chat).filter(id=self.chat_id).update(update_time=timezone.now()) # 插入会话记录 QuerySet(ChatRecord).update_or_create(id=chat_record.id, create_defaults={'id': chat_record.id, 'chat_id': chat_record.chat_id, "vote_status": chat_record.vote_status, 'problem_text': chat_record.problem_text, 'answer_text': chat_record.answer_text, 'answer_text_list': chat_record.answer_text_list, 'message_tokens': chat_record.message_tokens, 'answer_tokens': chat_record.answer_tokens, 'const': chat_record.const, 'details': chat_record.details, 'improve_paragraph_id_list': chat_record.improve_paragraph_id_list, 'run_time': chat_record.run_time, 'source': chat_record.source, 'ip_address': chat_record.ip_address or '', 'index': chat_record.index}, defaults={ "vote_status": chat_record.vote_status, 'problem_text': chat_record.problem_text, 'answer_text': chat_record.answer_text, 'answer_text_list': chat_record.answer_text_list, 'message_tokens': chat_record.message_tokens, 'answer_tokens': chat_record.answer_tokens, 'const': chat_record.const, 'details': chat_record.details, 'improve_paragraph_id_list': chat_record.improve_paragraph_id_list, 'run_time': chat_record.run_time, 'index': chat_record.index, 'source': chat_record.source, 'ip_address': chat_record.ip_address or '', }) ChatCountSerializer(data={'chat_id': self.chat_id}).update_chat() def to_dict(self): return { 'chat_id': self.chat_id, 'chat_user_id': self.chat_user_id, 'chat_user_type': self.chat_user_type, 'ip_address': self.ip_address, 'source': self.source, 'knowledge_id_list': self.knowledge_id_list, 'exclude_document_id_list': self.exclude_document_id_list, 'application_id': self.application_id, 'chat_record_list': [self.chat_record_to_map(c) for c in self.chat_record_list][-20:], 'debug': self.debug } def chat_record_to_map(self, chat_record): return {'id': chat_record.id, 'chat_id': chat_record.chat_id, 'vote_status': chat_record.vote_status, 'problem_text': chat_record.problem_text, 'answer_text': chat_record.answer_text, 'answer_text_list': chat_record.answer_text_list, 'message_tokens': chat_record.message_tokens, 'answer_tokens': chat_record.answer_tokens, 'const': chat_record.const, 'details': chat_record.details, 'improve_paragraph_id_list': chat_record.improve_paragraph_id_list, 'run_time': chat_record.run_time, 'source': chat_record.source, 'ip_address': chat_record.ip_address, 'index': chat_record.index} @staticmethod def map_to_chat_record(chat_record_dict): return ChatRecord(id=chat_record_dict.get('id'), chat_id=chat_record_dict.get('chat_id'), vote_status=chat_record_dict.get('vote_status'), problem_text=chat_record_dict.get('problem_text'), answer_text=chat_record_dict.get('answer_text'), answer_text_list=chat_record_dict.get('answer_text_list'), message_tokens=chat_record_dict.get('message_tokens'), answer_tokens=chat_record_dict.get('answer_tokens'), const=chat_record_dict.get('const'), details=chat_record_dict.get('details'), improve_paragraph_id_list=chat_record_dict.get('improve_paragraph_id_list'), run_time=chat_record_dict.get('run_time'), index=chat_record_dict.get('index'), source=chat_record_dict.get('source'), ip_address=chat_record_dict.get('ip_address')) def set_cache(self): cache.set(Cache_Version.CHAT.get_key(key=self.chat_id), self.to_dict(), version=Cache_Version.CHAT_INFO.get_version(), timeout=60 * 30) @staticmethod def map_to_chat_info(chat_info_dict): c = ChatInfo(chat_info_dict.get('chat_id'), chat_info_dict.get('chat_user_id'), chat_info_dict.get('chat_user_type'), chat_info_dict.get('ip_address'), chat_info_dict.get('source'), chat_info_dict.get('knowledge_id_list'), chat_info_dict.get('exclude_document_id_list'), chat_info_dict.get('application_id'), debug=chat_info_dict.get('debug')) c.chat_record_list = [ChatInfo.map_to_chat_record(c_r) for c_r in chat_info_dict.get('chat_record_list')] return c @staticmethod def get_cache(chat_id): chat_info_dict = cache.get(Cache_Version.CHAT.get_key(key=chat_id), version=Cache_Version.CHAT_INFO.get_version()) if chat_info_dict: return ChatInfo.map_to_chat_info(chat_info_dict) return None def update_resource_mapping_by_application(application_id: str, other_resource_mapping=None): from application.flow.tools import get_instance_resource, save_workflow_mapping, \ application_instance_field_call_dict from system_manage.models.resource_mapping import ResourceType if other_resource_mapping is None: other_resource_mapping = [] application = QuerySet(Application).filter(id=application_id).first() instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id), application_instance_field_call_dict) if application.type == 'WORK_FLOW': save_workflow_mapping(application.work_flow, ResourceType.APPLICATION, str(application_id), instance_mapping + other_resource_mapping) return else: save_workflow_mapping({}, ResourceType.APPLICATION, str(application_id), instance_mapping + other_resource_mapping) ================================================ FILE: apps/application/sql/chat_record_count_trend.sql ================================================ SELECT SUM ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num", "count"(application_chat_record."id") as chat_record_count, "count"(DISTINCT application_chat.chat_user_id) customer_num, application_chat_record.create_time :: DATE as "day" FROM application_chat_record application_chat_record LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id ${default_sql} GROUP BY "day" ================================================ FILE: apps/application/sql/count_chat_record.sql ================================================ SELECT COUNT ( "id" ) AS chat_record_count, SUM ( CASE WHEN "vote_status" = '0' THEN 1 ELSE 0 END ) AS star_num, SUM ( CASE WHEN "vote_status" = '1' THEN 1 ELSE 0 END ) AS trample_num, SUM ( CASE WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN 0 ELSE array_length( application_chat_record.improve_paragraph_id_list, 1 ) END ) AS mark_sum FROM application_chat_record ================================================ FILE: apps/application/sql/customer_count_trend.sql ================================================ SELECT COUNT ( "application_chat_user_stats"."id" ) AS "customer_added_count", create_time :: DATE as "day" FROM "application_chat_user_stats" ${default_sql} GROUP BY "day" ================================================ FILE: apps/application/sql/export_application_chat.sql ================================================ SELECT application_chat_record_temp.id AS id, application_chat."id" as chat_id, application_chat.abstract as abstract, application_chat_record_temp.problem_text as problem_text, application_chat_record_temp.answer_text as answer_text, application_chat_record_temp.message_tokens as message_tokens, application_chat_record_temp.answer_tokens as answer_tokens, application_chat_record_temp.run_time as run_time, application_chat_record_temp.details::JSON as details, application_chat_record_temp."index" as "index", application_chat_record_temp.improve_paragraph_list as improve_paragraph_list, application_chat_record_temp.vote_status as vote_status, application_chat_record_temp.vote_reason as vote_reason, application_chat_record_temp.vote_other_content as vote_other_content, application_chat_record_temp.create_time as create_time, application_chat.asker::json AS asker, application_chat_record_temp.ip_address as ip_address, application_chat_record_temp.source::json AS source FROM application_chat application_chat LEFT JOIN (SELECT *, CASE WHEN array_length(application_chat_record.improve_paragraph_id_list, 1) IS NULL THEN '{}' ELSE (SELECT ARRAY_AGG(row_to_json(paragraph)) FROM paragraph WHERE "id" = ANY (application_chat_record.improve_paragraph_id_list)) END as improve_paragraph_list FROM application_chat_record application_chat_record) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id" ${default_queryset} ================================================ FILE: apps/application/sql/export_application_chat_ee.sql ================================================ SELECT application_chat_record_temp.id AS id, application_chat."id" as chat_id, application_chat.abstract as abstract, application_chat_record_temp.problem_text as problem_text, application_chat_record_temp.answer_text as answer_text, application_chat_record_temp.message_tokens as message_tokens, application_chat_record_temp.answer_tokens as answer_tokens, application_chat_record_temp.run_time as run_time, application_chat_record_temp.details::JSON as details, application_chat_record_temp."index" as "index", application_chat_record_temp.improve_paragraph_list as improve_paragraph_list, application_chat_record_temp.vote_status as vote_status, application_chat_record_temp.vote_reason as vote_reason, application_chat_record_temp.vote_other_content as vote_other_content, application_chat_record_temp.create_time as create_time, (CASE WHEN "chat_user".id is NULL THEN application_chat.asker ELSE jsonb_build_object('id', chat_user.id, 'username', chat_user.username) END)::json AS asker, application_chat_record_temp.ip_address as ip_address, application_chat_record_temp.source::json AS source FROM application_chat application_chat left join chat_user chat_user on chat_user.id::varchar = application_chat.chat_user_id LEFT JOIN ( SELECT *, CASE WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN '{}' ELSE ( SELECT ARRAY_AGG ( row_to_json ( paragraph ) ) FROM paragraph WHERE "id" = ANY ( application_chat_record.improve_paragraph_id_list ) ) END as improve_paragraph_list FROM application_chat_record application_chat_record ) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id" ${default_queryset} ================================================ FILE: apps/application/sql/get_token_usage.sql ================================================ SELECT SUM(application_chat_record.message_tokens + application_chat_record.answer_tokens) as "token_usage", MAX(COALESCE(application_chat.asker ->>'username', '游客')) as "username" FROM application_chat_record application_chat_record LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id ${default_sql} GROUP BY application_chat.chat_user_id ORDER BY "token_usage" DESC ================================================ FILE: apps/application/sql/get_token_usage_ee.sql ================================================ SELECT SUM(application_chat_record.message_tokens + application_chat_record.answer_tokens) as "token_usage", MAX(COALESCE(chat_user.username, application_chat.asker ->>'username', '游客')) as "username" FROM application_chat_record application_chat_record LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id LEFT JOIN chat_user chat_user ON chat_user.id::varchar = application_chat.chat_user_id ${default_sql} GROUP BY application_chat.chat_user_id ORDER BY "token_usage" DESC ================================================ FILE: apps/application/sql/list_application.sql ================================================ select * from (select application."id"::text, application."name", application."desc", application."is_publish", application."type", 'application' as "resource_type", application."workspace_id", application."folder_id", application."user_id", "user"."nick_name" as "nick_name", application."create_time", application."update_time", application."publish_time", application.icon from application left join "user" on user_id = "user".id ${application_custom_sql} ) temp ${application_query_set} ================================================ FILE: apps/application/sql/list_application_chat.sql ================================================ select application_chat.*, application_chat.asker::json AS asker, application_chat.source::json AS source from application_chat application_chat ${default_queryset} ================================================ FILE: apps/application/sql/list_application_chat_ee.sql ================================================ select application_chat.*, (CASE WHEN "chat_user".id is NULL THEN application_chat.asker ELSE jsonb_build_object('id', chat_user.id, 'username', chat_user.username) END) ::json AS asker, application_chat.source::json AS source from application_chat application_chat left join chat_user chat_user on chat_user.id::varchar = application_chat.chat_user_id ${default_queryset} ================================================ FILE: apps/application/sql/list_application_user.sql ================================================ select * from (select application."id"::text, application."name", application."desc", application."is_publish", application."type", 'application' as "resource_type", application."workspace_id", application."folder_id", application."user_id", "user"."nick_name" as "nick_name", application."create_time", application."update_time", application."publish_time", application.icon from application left join "user" on user_id = "user".id where application."id"::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and 'VIEW' = any (permission_list))) temp ${application_query_set} ================================================ FILE: apps/application/sql/list_application_user_ee.sql ================================================ select * from (select application."id"::text, application."name", application."desc", application."is_publish", application."type", 'application' as "resource_type", application."workspace_id", application."folder_id", application."user_id", "user"."nick_name" as "nick_name", application."create_time", application."update_time", application."publish_time", application.icon from application left join "user" on user_id = "user".id where "application".id::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and case when auth_type = 'ROLE' then 'ROLE' = any (permission_list) and 'APPLICATION:READ' in (select (case when user_role_relation.role_id = any (array['USER']) THEN 'APPLICATION:READ' else role_permission.permission_id END) from role_permission role_permission right join user_role_relation user_role_relation on user_role_relation.role_id = role_permission.role_id where user_role_relation.user_id = workspace_user_resource_permission.user_id and user_role_relation.workspace_id = workspace_user_resource_permission.workspace_id) else 'VIEW' = any (permission_list) end)) temp ${application_query_set} ================================================ FILE: apps/application/sql/list_knowledge_paragraph_by_paragraph_id.sql ================================================ SELECT paragraph.*, knowledge."name" AS "knowledge_name", knowledge."type" AS "knowledge_type", "document"."name" AS "document_name", "document"."meta"::json AS "meta", "document"."hit_handling_method" AS "hit_handling_method", "document"."directly_return_similarity" as "directly_return_similarity" FROM paragraph paragraph LEFT JOIN knowledge knowledge ON knowledge."id" = paragraph.knowledge_id LEFT JOIN "document" "document" ON "document"."id" =paragraph.document_id ================================================ FILE: apps/application/sql/top_questions.sql ================================================ SELECT COUNT(application_chat_record."id") AS chat_record_count, MAX(COALESCE(application_chat.asker ->>'username', '游客')) as "username" FROM application_chat_record application_chat_record LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id ${default_sql} GROUP BY application_chat.chat_user_id ORDER BY chat_record_count DESC, username ASC ================================================ FILE: apps/application/sql/top_questions_ee.sql ================================================ SELECT COUNT(application_chat_record."id") AS chat_record_count, MAX(COALESCE(chat_user.username, application_chat.asker ->>'username', '游客')) as "username" FROM application_chat_record application_chat_record LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id LEFT JOIN chat_user chat_user ON chat_user.id::varchar = application_chat.chat_user_id ${default_sql} GROUP BY application_chat.chat_user_id ORDER BY chat_record_count DESC, username ASC ================================================ FILE: apps/application/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/application/urls.py ================================================ from django.urls import path from . import views app_name = 'application' # @formatter:off urlpatterns = [ path('workspace/store/application_template', views.ApplicationAPI.StoreApplication.as_view()), path('workspace//application', views.ApplicationAPI.as_view(), name='application'), path('workspace//application/folder//import', views.ApplicationAPI.Import.as_view()), path('workspace//application//', views.ApplicationAPI.Page.as_view(), name='application_page'), path('workspace//application/', views.ApplicationAPI.Operate.as_view()), path('workspace//application//publish', views.ApplicationAPI.Publish.as_view()), path('workspace//application//move/', views.ApplicationAPI.Move.as_view()), path('workspace//application//application_key', views.ApplicationKey.as_view()), path('workspace//application//application_stats', views.ApplicationStats.as_view()), path('workspace//application//application_token_usage', views.ApplicationStats.TokenUsageStatistics.as_view()), path('workspace//application//top_questions', views.ApplicationStats.TopQuestionsStatistics.as_view()), path('workspace//application//application_key/', views.ApplicationKey.Operate.as_view()), path('workspace//application//application_key//', views.ApplicationKey.Page.as_view()), path('workspace//application//export', views.ApplicationAPI.Export.as_view()), path('workspace//application//application_version', views.ApplicationVersionView.as_view()), path('workspace//application//access_token', views.AccessToken.as_view()), path('workspace//application//add_knowledge', views.ApplicationChatRecordAddKnowledge.as_view()), path('workspace//application//chat', views.ApplicationChat.as_view()), path('workspace//application//chat/export', views.ApplicationChat.Export.as_view()), path('workspace//application//chat//', views.ApplicationChat.Page.as_view()), path('workspace//application//chat//chat_record', views.ApplicationChatRecord.as_view()), path('workspace//application//chat//chat_record/', views.ApplicationChatRecordOperateAPI.as_view()), path('workspace//application//chat//chat_record//', views.ApplicationChatRecord.Page.as_view()), path('workspace//application//chat//chat_record//improve', views.ApplicationChatRecordImprove.as_view()), path('workspace//application//chat//chat_record//knowledge//document//improve', views.ApplicationChatRecordImproveParagraph.as_view()), path('workspace//application//chat//chat_record//knowledge//document//paragraph//improve', views.ApplicationChatRecordImproveParagraph.Operate.as_view()), path('workspace//application//application_version//', views.ApplicationVersionView.Page.as_view()), path('workspace//application//application_version/', views.ApplicationVersionView.Operate.as_view()), path('workspace//application//open', views.OpenView.as_view()), path('workspace//application//text_to_speech', views.TextToSpeech.as_view()), path('workspace//application//speech_to_text', views.SpeechToText.as_view()), path('workspace//application//play_demo_text', views.PlayDemoText.as_view()), path('workspace//application//mcp_tools', views.McpServers.as_view()), path('workspace//application//model//prompt_generate', views.PromptGenerateView.as_view()), path('chat_message/', views.ChatView.as_view()), ] ================================================ FILE: apps/application/views/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/5/9 18:51 @desc: """ from .application_api_key import * from .application import * from .application_version import * from .application_access_token import * from .application_stats import * from .application_chat import * from .application_chat_record import * from .application_chat_link import * ================================================ FILE: apps/application/views/application.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application.py @date:2025/5/26 16:51 @desc: """ from django.db.models import QuerySet from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI, ApplicationImportAPI, \ ApplicationExportAPI, ApplicationOperateAPI, ApplicationEditAPI, TextToSpeechAPI, SpeechToTextAPI, PlayDemoTextAPI from application.models import Application from application.serializers.application import ApplicationSerializer, Query, ApplicationOperateSerializer from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions, get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from tools.api.tool import GetInternalToolAPI def get_application_operation_object(application_id): application_model = QuerySet(model=Application).filter(id=application_id).first() if application_model is not None: return { 'name': application_model.name } return {} class ApplicationAPI(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create an application'), summary=_('Create an application'), operation_id=_('Create an application'), # type: ignore parameters=ApplicationCreateAPI.get_parameters(), request=ApplicationCreateAPI.get_request(), responses=ApplicationCreateAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CREATE.get_workspace_permission(), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate='Create an application', get_operation_object=lambda r, k: {'name': r.data.get('name')}, ) def post(self, request: Request, workspace_id: str): return result.success( ApplicationSerializer(data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data)) @extend_schema( methods=['GET'], description=_('Get the application list'), summary=_('Get the application list'), operation_id=_('Get the application list'), # type: ignore parameters=ApplicationQueryAPI.get_parameters(), responses=ApplicationQueryAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission(), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str): return result.success( Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).list(request.query_params)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get the application list by page'), summary=_('Get the application list by page'), operation_id=_('Get the application list by page'), # type: ignore parameters=ApplicationQueryAPI.get_parameters(), responses=ApplicationQueryAPI.get_page_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission(), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, current_page: int, page_size: int): return result.success( Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size, request.query_params)) class Import(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['POST'], description=_('Import Application'), summary=_('Import Application'), operation_id=_('Import Application'), # type: ignore parameters=ApplicationImportAPI.get_parameters(), request=ApplicationImportAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_IMPORT.get_workspace_permission(), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Import Application", ) def post(self, request: Request, workspace_id: str, folder_id: str): is_import_tool = get_is_permissions(request, workspace_id=workspace_id, folder_id=folder_id)( PermissionConstants.TOOL_IMPORT.get_workspace_permission(), PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) return result.success(ApplicationSerializer( data={'user_id': request.user.id, 'workspace_id': workspace_id, }).import_({'file': request.FILES.get('file'), 'folder_id': folder_id}, is_import_tool)) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Export application'), summary=_('Export application'), operation_id=_('Export application'), # type: ignore parameters=ApplicationExportAPI.get_parameters(), request=None, responses=ApplicationExportAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EXPORT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EXPORT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Export Application", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def get(self, request: Request, workspace_id: str, application_id: str): return ApplicationOperateSerializer( data={'application_id': application_id, 'workspace_id': workspace_id, 'user_id': request.user.id}).export() class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['DELETE'], description=_('Deleting application'), summary=_('Deleting application'), operation_id=_('Deleting application'), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_DELETE.get_workspace_application_permission(), PermissionConstants.APPLICATION_DELETE.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate='Deleting application', get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def delete(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).delete( with_valid=True)) @extend_schema( methods=['PUT'], description=_('Modify the application'), summary=_('Modify the application'), operation_id=_('Modify the application'), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), request=ApplicationEditAPI.get_request(), responses=ApplicationCreateAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Modify the application", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def put(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).edit( request.data)) @extend_schema( methods=['GET'], description=_('Get application details'), summary=_('Get application details'), operation_id=_('Get application details'), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), request=ApplicationEditAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).one()) class Move(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Move an application"), summary=_("Move an application"), operation_id=_("Move an application"), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), request=None, responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate='Move an application', get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id'))) def put(self, request: Request, workspace_id: str, application_id: str, folder_id: str): return result.success( ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).move(folder_id)) class Publish(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Publishing an application"), summary=_("Publishing an application"), operation_id=_("Publishing an application"), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), request=None, responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate='Publishing an application', get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id'))) def put(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).publish(request.data)) class StoreApplication(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get Appstore apps"), summary=_("Get Appstore apps"), operation_id=_("Get Appstore apps"), # type: ignore responses=GetInternalToolAPI.get_response(), tags=[_("Application")] # type: ignore ) def get(self, request: Request): return result.success(ApplicationSerializer.StoreApplication(data={ 'user_id': request.user.id, 'name': request.query_params.get('name', ''), }).get_appstore_templates()) class McpServers(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("speech to text"), summary=_("speech to text"), operation_id=_("speech to text"), # type: ignore parameters=SpeechToTextAPI.get_parameters(), request=SpeechToTextAPI.get_request(), responses=SpeechToTextAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id, application_id: str): return result.success(ApplicationOperateSerializer( data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id, 'user_id': request.user.id, 'application_id': application_id}).get_mcp_servers(request.data)) class SpeechToText(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("speech to text"), summary=_("speech to text"), operation_id=_("speech to text"), # type: ignore parameters=SpeechToTextAPI.get_parameters(), request=SpeechToTextAPI.get_request(), responses=SpeechToTextAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationOperateSerializer( data={'application_id': application_id, 'workspace_id': workspace_id, 'user_id': request.user.id}) .speech_to_text({'file': request.FILES.get('file')})) class TextToSpeech(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("text to speech"), summary=_("text to speech"), operation_id=_("text to speech"), # type: ignore parameters=TextToSpeechAPI.get_parameters(), request=TextToSpeechAPI.get_request(), responses=TextToSpeechAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id: str, application_id: str): byte_data = ApplicationOperateSerializer( data={'application_id': application_id, 'workspace_id': workspace_id, 'user_id': request.user.id}).text_to_speech(request.data) return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3', 'Content-Disposition': 'attachment; filename="abc.mp3"'}) class PlayDemoText(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("PlayDemo"), summary=_("PlayDemo"), operation_id=_("PlayDemo"), # type: ignore parameters=PlayDemoTextAPI.get_parameters(), request=PlayDemoTextAPI.get_request(), responses=PlayDemoTextAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="trial listening", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id'))) def post(self, request: Request, workspace_id: str, application_id: str): byte_data = ApplicationOperateSerializer( data={'application_id': application_id, 'workspace_id': workspace_id, 'user_id': request.user.id}).play_demo_text(request.data) return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3', 'Content-Disposition': 'attachment; filename="abc.mp3"'}) ================================================ FILE: apps/application/views/application_access_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_token.py @date:2025/6/9 17:42 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_access_token import ApplicationAccessTokenAPI from application.models import Application from application.serializers.application_access_token import AccessTokenSerializer from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log def get_application_operation_object(application_id): application_model = QuerySet(model=Application).filter(id=application_id).first() if application_model is not None: return { "name": application_model.name } return {} class AccessToken(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Modify application access restriction information"), summary=_("Modify application access restriction information"), operation_id=_("Modify application access restriction information"), # type: ignore parameters=ApplicationAccessTokenAPI.get_parameters(), request=ApplicationAccessTokenAPI.get_request(), tags=[_('Application')] # type: ignore ) @log(menu='Application', operate="Modify application access token", get_operation_object= lambda r,k: get_application_operation_object((k.get('application_id'))) ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_ACCESS.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_ACCESS.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def put(self, request: Request, workspace_id: str, application_id: str): return result.success( AccessTokenSerializer(data={'workspace_id': workspace_id, 'application_id': application_id}).edit( request.data)) @extend_schema( methods=['GET'], description=_("Get application access restriction information"), summary=_("Get application access restriction information"), operation_id=_("Get application access restriction information"), # type: ignore parameters=ApplicationAccessTokenAPI.get_parameters(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role() ) def get(self, request: Request, workspace_id: str, application_id: str): return result.success( AccessTokenSerializer(data={'workspace_id': workspace_id, 'application_id': application_id}).one()) ================================================ FILE: apps/application/views/application_api_key.py ================================================ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_api_key import ApplicationKeyAPI from application.models import Application from application.serializers.application_api_key import ApplicationKeySerializer from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result, DefaultResultSerializer def get_application_operation_object(application_id): application_model = QuerySet(model=Application).filter(id=application_id).first() if application_model is not None: return { "name": application_model.name } return {} class ApplicationKey(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create application ApiKey'), summary=_('Create application ApiKey'), operation_id=_('Create application ApiKey'), # type: ignore parameters=ApplicationKeyAPI.get_parameters(), request=None, responses=ApplicationKeyAPI.get_response(), tags=[_('Application Api Key')] # type: ignore ) @log(menu='Application', operate="Add ApiKey", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role() ) def post(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationKeySerializer( data={'application_id': application_id, 'workspace_id': workspace_id}).generate()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('GET application ApiKey List'), summary=_('Create application ApiKey List'), operation_id=_('Create application ApiKey List'), # type: ignore parameters=ApplicationKeyAPI.get_parameters(), responses=ApplicationKeyAPI.List.get_response(), tags=[_('Application Api Key')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int): return result.success(ApplicationKeySerializer( data={'application_id': application_id, 'workspace_id': workspace_id, 'order_by': request.query_params.get('order_by')}).page(current_page, page_size)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Modify application API_KEY'), summary=_('Modify application API_KEY'), operation_id=_('Modify application API_KEY'), # type: ignore parameters=ApplicationKeyAPI.Operate.get_parameters(), request=ApplicationKeyAPI.Operate.get_request(), responses=DefaultResultSerializer, tags=[_('Application Api Key')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Modify application API_KEY", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def put(self, request: Request, workspace_id: str, application_id: str, api_key_id: str): return result.success( ApplicationKeySerializer.Operate( data={'workspace_id': workspace_id, 'application_id': application_id, 'api_key_id': api_key_id}).edit( request.data)) @extend_schema( methods=['DELETE'], description=_('Delete Application API_KEY'), summary=_('Delete Application API_KEY'), operation_id=_('Delete Application API_KEY'), # type: ignore parameters=ApplicationKeyAPI.Operate.get_parameters(), request=ApplicationKeyAPI.Operate.get_request(), responses=DefaultResultSerializer, tags=[_('Application Api Key')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Delete application API_KEY", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def delete(self, request: Request, workspace_id: str, application_id: str, api_key_id: str): return result.success( ApplicationKeySerializer.Operate( data={'workspace_id': workspace_id, 'application_id': application_id, 'api_key_id': api_key_id}).delete()) ================================================ FILE: apps/application/views/application_chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat.py @date:2025/6/10 11:00 @desc: """ import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_chat import ApplicationChatQueryAPI, ApplicationChatQueryPageAPI, \ ApplicationChatExportAPI from application.models import ChatUserType, Application from application.serializers.application_chat import ApplicationChatQuerySerializers from chat.api.chat_api import ChatAPI, PromptGenerateAPI from chat.api.chat_authentication_api import ChatOpenAPI from chat.serializers.chat import OpenChatSerializers, ChatSerializers, DebugChatSerializers, PromptGenerateSerializer from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from common.utils.common import query_params_to_single_dict def get_application_operation_object(application_id): application_model = QuerySet(model=Application).filter(id=application_id).first() if application_model is not None: return { 'name': application_model.name } return {} class ApplicationChat(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the conversation list"), summary=_("Get the conversation list"), operation_id=_("Get the conversation list"), # type: ignore request=ApplicationChatQueryAPI.get_request(), parameters=ApplicationChatQueryAPI.get_parameters(), responses=ApplicationChatQueryAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationChatQuerySerializers( data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'application_id': application_id, }).list()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the conversation list by page"), summary=_("Get the conversation list by page"), operation_id=_("Get the conversation list by page"), # type: ignore request=ApplicationChatQueryPageAPI.get_request(), parameters=ApplicationChatQueryPageAPI.get_parameters(), responses=ApplicationChatQueryPageAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int): return result.success(ApplicationChatQuerySerializers( data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'application_id': application_id, }).page(current_page=current_page, page_size=page_size)) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Export conversation"), summary=_("Export conversation"), operation_id=_("Export conversation"), # type: ignore request=ApplicationChatExportAPI.get_request(), parameters=ApplicationChatExportAPI.get_parameters(), responses=ApplicationChatExportAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_EXPORT.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_EXPORT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id: str, application_id: str): return ApplicationChatQuerySerializers( data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'application_id': application_id, }).export(request.data) class OpenView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get a temporary session id based on the application id"), summary=_("Get a temporary session id based on the application id"), operation_id=_("Get a temporary session id based on the application id"), # type: ignore parameters=ChatOpenAPI.get_parameters(), responses=None, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success(OpenChatSerializers( data={'workspace_id': workspace_id, 'application_id': application_id, 'chat_user_id': str(uuid.uuid7()), 'chat_user_type': ChatUserType.ANONYMOUS_USER, 'debug': True}).open()) class ChatView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("dialogue"), summary=_("dialogue"), operation_id=_("dialogue"), # type: ignore request=ChatAPI.get_request(), parameters=ChatAPI.get_parameters(), responses=None, tags=[_('Application')] # type: ignore ) def post(self, request: Request, chat_id: str): return DebugChatSerializers(data={'chat_id': chat_id}).chat(request.data) class PromptGenerateView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("generate prompt"), summary=_("generate prompt"), operation_id=_("generate prompt"), # type: ignore request=PromptGenerateAPI.get_request(), parameters=PromptGenerateAPI.get_parameters(), responses=None, tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate='Generate prompt', get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id'))) def post(self, request: Request, workspace_id: str, model_id:str, application_id: str): return PromptGenerateSerializer(data={'workspace_id': workspace_id, 'model_id': model_id, 'application_id': application_id}).generate_prompt(instance=request.data) ================================================ FILE: apps/application/views/application_chat_link.py ================================================ """ @project: MaxKB @Author: niu @file: application_chat_link.py @date: 2026/2/9 10:44 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_chat_link import ChatRecordLinkAPI, ChatRecordDetailShareAPI from application.serializers.application_chat_link import ChatRecordShareLinkSerializer, ChatShareLinkDetailSerializer from common import result from common.auth import ChatTokenAuth class ChatRecordLinkView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['POST'], description=_("Generate share link"), summary=_("Generate share link"), operation_id=_("Generate share link"), # type: ignore request=ChatRecordLinkAPI.get_request(), parameters=ChatRecordLinkAPI.get_parameters(), responses=ChatRecordLinkAPI.get_response(), tags=[_("Chat record link")] # type: ignore ) def post(self, request: Request, application_id: str, chat_id: str): return result.success(ChatRecordShareLinkSerializer(data={ "application_id": application_id, "chat_id": chat_id, "user_id": request.auth.chat_user_id }).generate_link(request.data)) class ChatRecordDetailView(APIView): @extend_schema( methods=['GET'], description=_("Get chat record by share link"), summary=_("Get chat record by share link"), operation_id=_("Get chat record by share link"), # type: ignore parameters=ChatRecordDetailShareAPI.get_parameters(), responses=ChatRecordDetailShareAPI.get_response(), tags=[_("Chat record link")] # type: ignore ) def get(self, request, link: str): return result.success( ChatShareLinkDetailSerializer(data={'link':link}).get_record_list() ) ================================================ FILE: apps/application/views/application_chat_record.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_chat_record.py @date:2025/6/10 15:08 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_chat_record import ApplicationChatRecordQueryAPI, \ ApplicationChatRecordImproveParagraphAPI, ApplicationChatRecordAddKnowledgeAPI from application.serializers.application_chat_record import ApplicationChatRecordQuerySerializers, \ ApplicationChatRecordImproveSerializer, ChatRecordImproveSerializer, ApplicationChatRecordAddKnowledgeSerializer, \ ChatRecordOperateSerializer from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.utils.common import query_params_to_single_dict class ApplicationChatRecord(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the conversation record list"), summary=_("Get the conversation record list"), operation_id=_("Get the conversation record list"), # type: ignore request=ApplicationChatRecordQueryAPI.get_request(), parameters=ApplicationChatRecordQueryAPI.get_parameters(), responses=ApplicationChatRecordQueryAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str): return result.success(ApplicationChatRecordQuerySerializers( data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id }).list()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the conversation record list by page"), summary=_("Get the conversation record list by page"), operation_id=_("Get the conversation record list by page"), # type: ignore request=ApplicationChatRecordQueryAPI.get_request(), parameters=ApplicationChatRecordQueryAPI.get_parameters(), responses=ApplicationChatRecordQueryAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, current_page: int, page_size: int): return result.success(ApplicationChatRecordQuerySerializers( data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id}).page( current_page=current_page, page_size=page_size)) class ApplicationChatRecordOperateAPI(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get conversation record details"), summary=_("Get conversation record details"), operation_id=_("Get conversation record details"), # type: ignore request=ApplicationChatRecordQueryAPI.get_request(), parameters=ApplicationChatRecordQueryAPI.get_parameters(), responses=ApplicationChatRecordQueryAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str): return result.success(ChatRecordOperateSerializer( data={ 'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id, 'chat_record_id': chat_record_id}).one(True)) class ApplicationChatRecordAddKnowledge(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Add to Knowledge Base"), summary=_("Add to Knowledge Base"), operation_id=_("Add to Knowledge Base"), # type: ignore request=ApplicationChatRecordAddKnowledgeAPI.get_request(), parameters=ApplicationChatRecordAddKnowledgeAPI.get_parameters(), responses=ApplicationChatRecordAddKnowledgeAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationChatRecordAddKnowledgeSerializer(data = {'workspace_id': workspace_id, 'application_id': application_id, **request.data}).post_improve( {'workspace_id': workspace_id, 'application_id': application_id, **request.data}, request=request)) class ApplicationChatRecordImprove(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the list of marked paragraphs"), summary=_("Get the list of marked paragraphs"), operation_id=_("Get the list of marked paragraphs"), # type: ignore request=ApplicationChatRecordQueryAPI.get_request(), parameters=ApplicationChatRecordQueryAPI.get_parameters(), responses=ApplicationChatRecordQueryAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str): return result.success(ChatRecordImproveSerializer( data={'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id, 'chat_record_id': chat_record_id}).get()) class ApplicationChatRecordImproveParagraph(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Annotation"), summary=_("Annotation"), operation_id=_("Annotation"), # type: ignore request=ApplicationChatRecordImproveParagraphAPI.get_request(), parameters=ApplicationChatRecordImproveParagraphAPI.get_parameters(), responses=ApplicationChatRecordImproveParagraphAPI.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def put(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str, knowledge_id: str, document_id: str): return result.success(ApplicationChatRecordImproveSerializer( data={'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id, 'chat_record_id': chat_record_id, 'knowledge_id': knowledge_id, 'document_id': document_id}).improve(request.data, request=request)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['DELETE'], description=_("Delete a Annotation"), summary=_("Delete a Annotation"), operation_id=_("Delete a Annotation"), # type: ignore request=ApplicationChatRecordImproveParagraphAPI.Operate.get_request(), parameters=ApplicationChatRecordImproveParagraphAPI.Operate.get_parameters(), responses=ApplicationChatRecordImproveParagraphAPI.Operate.get_response(), tags=[_("Application/Conversation Log")] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(), PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def delete(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str, knowledge_id: str, document_id: str, paragraph_id: str): return result.success(ApplicationChatRecordImproveSerializer.Operate( data={'chat_id': chat_id, 'chat_record_id': chat_record_id, 'workspace_id': workspace_id, 'application_id': application_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'paragraph_id': paragraph_id}).delete(request=request)) ================================================ FILE: apps/application/views/application_stats.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_stats.py @date:2025/6/9 20:30 @desc: """ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_stats import ApplicationStatsAPI from application.serializers.application_stats import ApplicationStatisticsSerializer from common import result from common.auth import TokenAuth from django.utils.translation import gettext_lazy as _ from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants class ApplicationStats(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Dialogue-related statistical trends'), summary=_('Dialogue-related statistical trends'), operation_id=_('Dialogue-related statistical trends'), # type: ignore parameters=ApplicationStatsAPI.get_parameters(), responses=ApplicationStatsAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id, 'start_time': request.query_params.get( 'start_time'), 'end_time': request.query_params.get( 'end_time') }).get_chat_record_aggregate_trend()) class TokenUsageStatistics(APIView): authentication_classes = [TokenAuth] # 应用的token使用统计 根据人的使用数排序 @extend_schema( methods=['GET'], description=_('Application token usage statistics'), summary=_('Application token usage statistics'), operation_id=_('Application token usage statistics'), # type: ignore parameters=ApplicationStatsAPI.get_parameters(), responses=ApplicationStatsAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id, 'start_time': request.query_params.get( 'start_time'), 'end_time': request.query_params.get( 'end_time') }).get_token_usage_statistics()) class TopQuestionsStatistics(APIView): authentication_classes = [TokenAuth] # 应用的top问题统计 @extend_schema( methods=['GET'], description=_('Application top question statistics'), summary=_('Application top question statistics'), operation_id=_('Application top question statistics'), # type: ignore parameters=ApplicationStatsAPI.get_parameters(), responses=ApplicationStatsAPI.get_response(), tags=[_('Application')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id, 'start_time': request.query_params.get( 'start_time'), 'end_time': request.query_params.get( 'end_time') }).get_top_questions_statistics()) ================================================ FILE: apps/application/views/application_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py.py @date:2025/6/3 15:46 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_version import ApplicationVersionListAPI, ApplicationVersionPageAPI, \ ApplicationVersionOperateAPI from application.serializers.application_version import ApplicationVersionSerializer from application.views import get_application_operation_object from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log class ApplicationVersionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the application version list"), summary=_("Get the application version list"), operation_id=_("Get the application version list"), # type: ignore parameters=ApplicationVersionListAPI.get_parameters(), responses=ApplicationVersionListAPI.get_response(), tags=[_('Application/Version')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id, application_id: str): return result.success( ApplicationVersionSerializer.Query( data={'workspace_id': workspace_id}).list( {'name': request.query_params.get("name"), 'application_id': application_id})) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the list of application versions by page"), summary=_("Get the list of application versions by page"), operation_id=_("Get the list of application versions by page"), # type: ignore parameters=ApplicationVersionPageAPI.get_parameters(), responses=ApplicationVersionPageAPI.get_response(), tags=[_('Application/Version')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int): return result.success( ApplicationVersionSerializer.Query( data={'workspace_id': workspace_id}).page( {'name': request.query_params.get("name"), 'application_id': application_id}, current_page, page_size)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get application version details"), summary=_("Get application version details"), operation_id=_("Get application version details"), # type: ignore parameters=ApplicationVersionOperateAPI.get_parameters(), responses=ApplicationVersionOperateAPI.get_response(), tags=[_('Application/Version')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, application_version_id: str): return result.success( ApplicationVersionSerializer.Operate( data={'user_id': request.user, 'workspace_id': workspace_id, 'application_id': application_id, 'application_version_id': application_version_id}).one()) @extend_schema( methods=['PUT'], description=_("Modify application version information"), summary=_("Modify application version information"), operation_id=_("Modify application version information"), # type: ignore parameters=ApplicationVersionOperateAPI.get_parameters(), request=None, responses=ApplicationVersionOperateAPI.get_response(), tags=[_('Application/Version')] # type: ignore ) @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Modify application version information", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) def put(self, request: Request, workspace_id: str, application_id: str, application_version_id: str): return result.success( ApplicationVersionSerializer.Operate( data={'application_id': application_id, 'workspace_id': workspace_id, 'application_version_id': application_version_id, 'user_id': request.user.id}).edit( request.data)) ================================================ FILE: apps/chat/__init__.py ================================================ ================================================ FILE: apps/chat/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/chat/api/chat_api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_api.py @date:2025/6/9 15:23 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_chat_record import ChatRecordSerializerModel from chat.serializers.chat import ChatMessageSerializers, GeneratePromptSerializers from chat.serializers.chat_record import HistoryChatModel, EditAbstractSerializer from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer class PromptGenerateAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="model_id", description="模型id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="application_id", description="应用id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return GeneratePromptSerializers class ChatAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="chat_id", description="对话id", type=OpenApiTypes.STR, location='path', required=True, )] @staticmethod def get_request(): return ChatMessageSerializers class ApplicationCreateResponse(ResultSerializer): def get_data(self): return HistoryChatModel(many=True) class PageApplicationCreateResponse(ResultPageSerializer): def get_data(self): return HistoryChatModel(many=True) class ApplicationRecordResponse(ResultSerializer): def get_data(self): return ChatRecordSerializerModel(many=True) class PageApplicationRecordResponse(ResultPageSerializer): def get_data(self): return ChatRecordSerializerModel(many=True) class HistoricalConversationAPI(APIMixin): @staticmethod def get_parameters(): return [] @staticmethod def get_response(): return ApplicationCreateResponse class PageHistoricalConversationAPI(APIMixin): @staticmethod def get_parameters(): return [] @staticmethod def get_response(): return PageApplicationCreateResponse class HistoricalConversationOperateAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="chat_id", description="对话id", type=OpenApiTypes.STR, location='path', required=True )] @staticmethod def get_request(): return EditAbstractSerializer @staticmethod def get_response(): return DefaultResultSerializer class HistoricalConversationRecordAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="chat_id", description="对话id", type=OpenApiTypes.STR, location='path', required=True, )] @staticmethod def get_response(): return ApplicationRecordResponse class PageHistoricalConversationRecordAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="chat_id", description="对话id", type=OpenApiTypes.STR, location='path', required=True, )] @staticmethod def get_response(): return PageApplicationRecordResponse ================================================ FILE: apps/chat/api/chat_authentication_api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_authentication_api.py @date:2025/6/6 19:59 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from chat.serializers.chat import OpenAIInstanceSerializer from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer from common.mixins.api_mixin import APIMixin class OpenAIAPI(APIMixin): @staticmethod def get_request(): return OpenAIInstanceSerializer class ChatAuthenticationAPI(APIMixin): @staticmethod def get_request(): return AnonymousAuthenticationSerializer @staticmethod def get_parameters(): pass @staticmethod def get_response(): pass class ChatAuthenticationProfileAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="access_token", description=_("access_token"), type=OpenApiTypes.STR, location='query', required=True, )] class ChatOpenAPI(APIMixin): @staticmethod def get_parameters(): return [] ================================================ FILE: apps/chat/api/chat_embed_api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_embed_api.py @date:2025/5/30 15:25 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from django.utils.translation import gettext_lazy as _ from common.result import DefaultResultSerializer class ChatEmbedAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="host", description=_("host"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="protocol", description=_("protocol"), type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="token", description=_("token"), type=OpenApiTypes.STR, location='query', required=False, ) ] @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/chat/api/vote_api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: vote_api.py @date:2025/6/23 17:35 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from chat.serializers.chat_record import VoteRequest from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer class VoteAPI(APIMixin): @staticmethod def get_request(): return VoteRequest @staticmethod def get_parameters(): return [OpenApiParameter( name="chat_id", description=_("Chat ID"), type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="chat_record_id", description=_("Chat Record ID"), type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/chat/apps.py ================================================ from django.apps import AppConfig class ChatConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'chat' ================================================ FILE: apps/chat/mcp/__init__.py ================================================ ================================================ FILE: apps/chat/mcp/tools.py ================================================ import json import uuid_utils.compat as uuid from django.db.models import QuerySet from application.models import ApplicationApiKey, Application, ChatUserType, ChatSourceChoices from chat.serializers.chat import ChatSerializers class MCPToolHandler: def __init__(self, auth_header): app_key = QuerySet(ApplicationApiKey).filter(secret_key=auth_header, is_active=True).first() if not app_key: raise PermissionError("Invalid API Key") self.application = QuerySet(Application).filter(id=app_key.application_id, is_publish=True).first() if not self.application: raise PermissionError("Application is not found or not published") def initialize(self): return { "protocolVersion": "2025-06-18", "serverInfo": { "name": "maxkb-mcp", "version": "1.0.0" }, "capabilities": { "tools": {} } } def list_tools(self): return { "tools": [ { "name": f'agent_{str(self.application.id)[:8]}', "description": f'{self.application.name} {self.application.desc}', "inputSchema": { "type": "object", "properties": { "message": {"type": "string", "description": "The message to send to the AI."}, }, "required": ["message"] } } ] } def _get_chat_id(self): from application.models import ChatUserType from chat.serializers.chat import OpenChatSerializers from common.init import init_template init_template.run() return OpenChatSerializers(data={ 'application_id': self.application.id, 'chat_user_id': str(uuid.uuid7()), 'chat_user_type': ChatUserType.ANONYMOUS_USER, 'ip_address': '-', 'source': {"type": ChatSourceChoices.ONLINE.value}, 'debug': False }).open() def call_tool(self, params): name = params["name"] args = params.get("arguments", {}) # print(params) payload = { 'message': args.get('message'), 'stream': False, 're_chat': False } resp = ChatSerializers(data={ 'chat_id': self._get_chat_id(), 'chat_user_id': str(uuid.uuid7()), 'chat_user_type': ChatUserType.ANONYMOUS_USER, 'application_id': self.application.id, 'ip_address': '-', 'source': {"type": ChatSourceChoices.ONLINE.value}, 'debug': False, }).chat(payload) data = json.loads(str(resp.text)) return {"content": [{"type": "text", "text": data.get('data', {}).get('content')}]} ================================================ FILE: apps/chat/migrations/__init__.py ================================================ ================================================ FILE: apps/chat/models/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/5/29 16:08 @desc: """ ================================================ FILE: apps/chat/serializers/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/5/29 16:08 @desc: """ ================================================ FILE: apps/chat/serializers/chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat.py @date:2025/6/9 11:23 @desc: """ import json import os from gettext import gettext from typing import List, Dict import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage from rest_framework import serializers from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.step.chat_step.i_chat_step import PostResponseHandler from application.chat_pipeline.step.chat_step.impl.base_chat_step import BaseChatStep from application.chat_pipeline.step.generate_human_message_step.impl.base_generate_human_message_step import \ BaseGenerateHumanMessageStep from application.chat_pipeline.step.reset_problem_step.impl.base_reset_problem_step import BaseResetProblemStep from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep from application.flow.common import Answer, Workflow from application.flow.i_step_node import WorkFlowPostHandler from application.flow.tools import to_stream_response_simple from application.flow.workflow_manage import WorkflowManage from application.models import Application, ApplicationTypeChoices, \ ChatUserType, ApplicationChatUserStats, ApplicationAccessToken, ChatRecord, Chat, ApplicationVersion from application.serializers.application import ApplicationOperateSerializer from application.serializers.common import ChatInfo from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.openai_to_response import OpenaiToResponse from common.handle.impl.response.system_to_response import SystemToResponse from common.utils.common import flat_map, get_file_content, is_valid_uuid from knowledge.models import Document, Paragraph from maxkb.conf import PROJECT_DIR from models_provider.models import Model, Status from models_provider.tools import get_model_instance_by_model_workspace_id from system_manage.models.resource_mapping import ResourceMapping class ChatMessagesSerializers(serializers.Serializer): role = serializers.CharField(required=True, label=_("Role")) content = serializers.CharField(required=True, label=_("Content")) class GeneratePromptSerializers(serializers.Serializer): prompt = serializers.CharField(required=True, label=_("Prompt template")) messages = serializers.ListSerializer(child=ChatMessagesSerializers(), required=True, label=_("Chat context")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) messages = self.data.get("messages") if len(messages) > 30: raise AppApiException(400, _("Too many messages")) for index in range(len(messages)): role = messages[index].get('role') if role == 'ai' and index % 2 != 1: raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) if role == 'user' and index % 2 != 0: raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) if role not in ['user', 'ai']: raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) class ChatMessageSerializers(serializers.Serializer): message = serializers.CharField(required=True, label=_("User Questions")) stream = serializers.BooleanField(required=True, label=_("Is the answer in streaming mode")) re_chat = serializers.BooleanField(required=True, label=_("Do you want to reply again")) chat_record_id = serializers.UUIDField(required=False, allow_null=True, label=_("Conversation record id")) node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Node id")) runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Runtime node id")) node_data = serializers.DictField(required=False, allow_null=True, label=_("Node parameters")) form_data = serializers.DictField(required=False, label=_("Global variables")) image_list = serializers.ListField(required=False, label=_("picture")) document_list = serializers.ListField(required=False, label=_("document")) audio_list = serializers.ListField(required=False, label=_("Audio")) other_list = serializers.ListField(required=False, label=_("Other")) child_node = serializers.DictField(required=False, allow_null=True, label=_("Child Nodes")) def get_post_handler(chat_info: ChatInfo): class PostHandler(PostResponseHandler): def handler(self, chat_id, chat_record_id, paragraph_list: List[Paragraph], problem_text: str, answer_text, manage: PipelineManage, step: BaseChatStep, padding_problem_text: str = None, **kwargs): answer_list = [[Answer(answer_text, 'ai-chat-node', 'ai-chat-node', 'ai-chat-node', {}, 'ai-chat-node', kwargs.get('reasoning_content', '')).to_dict()]] chat_record = ChatRecord(id=chat_record_id, chat_id=chat_id, problem_text=problem_text, answer_text=answer_text, details=manage.get_details(), message_tokens=manage.context['message_tokens'], answer_tokens=manage.context['answer_tokens'], answer_text_list=answer_list, run_time=manage.context['run_time'], index=len(chat_info.chat_record_list) + 1, ip_address=chat_info.ip_address, source=chat_info.source ) chat_info.append_chat_record(chat_record) # 重新设置缓存 chat_info.set_cache() return PostHandler() class DebugChatSerializers(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) def chat(self, instance: dict, base_to_response: BaseToResponse = SystemToResponse()): self.is_valid(raise_exception=True) chat_id = self.data.get('chat_id') chat_info: ChatInfo = ChatInfo.get_cache(chat_id) application = QuerySet(Application).filter(id=chat_info.application_id).first() chat_info.application = application return ChatSerializers(data={ 'chat_id': chat_id, "chat_user_id": chat_info.chat_user_id, "chat_user_type": chat_info.chat_user_type, "application_id": chat_info.application.id, "debug": True }).chat(instance, base_to_response) SYSTEM_ROLE = get_file_content(os.path.join(PROJECT_DIR, "apps", "chat", 'template', 'generate_prompt_system')) class PromptGenerateSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('Workspace ID')) model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model")) application_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Application")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Application).filter(id=self.data.get('application_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) application = query_set.first() if application is None: raise AppApiException(500, _('Application id does not exist')) return application def generate_prompt(self, instance: dict): application = self.is_valid(raise_exception=True) GeneratePromptSerializers(data=instance).is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') model_id = self.data.get('model_id') prompt = instance.get('prompt') messages = instance.get('messages') message = messages[-1]['content'] q = prompt.replace("{userInput}", message) messages[-1]['content'] = q SUPPORTED_MODEL_TYPES = ["LLM", "IMAGE"] model_exist = QuerySet(Model).filter( id=model_id, model_type__in=SUPPORTED_MODEL_TYPES ).exists() if not model_exist: raise Exception(_("Model does not exists or is not an LLM model")) def process(): model = get_model_instance_by_model_workspace_id(model_id=model_id, workspace_id=workspace_id, **application.model_params_setting) try: for r in model.stream([SystemMessage(content=SYSTEM_ROLE), *[HumanMessage(content=m.get('content')) if m.get( 'role') == 'user' else AIMessage( content=m.get('content')) for m in messages]]): yield 'data: ' + json.dumps({'content': r.content}) + '\n\n' except Exception as e: yield 'data: ' + json.dumps({'error': str(e)}) + '\n\n' return to_stream_response_simple(process()) class OpenAIMessage(serializers.Serializer): content = serializers.CharField(required=True, label=_('content')) role = serializers.CharField(required=True, label=_('Role')) class OpenAIInstanceSerializer(serializers.Serializer): messages = serializers.ListField(child=OpenAIMessage()) chat_id = serializers.UUIDField(required=False, label=_("Conversation ID")) re_chat = serializers.BooleanField(required=False, label=_("Regenerate")) stream = serializers.BooleanField(required=False, label=_("Streaming Output")) class OpenAIChatSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) chat_user_id = serializers.CharField(required=True, label=_("Client id")) chat_user_type = serializers.CharField(required=True, label=_("Client Type")) ip_address = serializers.CharField(required=False, label=_("IP Address")) source = serializers.JSONField(required=False, label=_("Source")) @staticmethod def get_message(instance): return instance.get('messages')[-1].get('content') @staticmethod def generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type, ip_address, source): if chat_id is None: chat_id = str(uuid.uuid1()) chat_info = ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, [], [], application_id) chat_info.set_cache() else: chat_info = ChatInfo.get_cache(chat_id) if chat_info is None: open_chat = ChatSerializers(data={ 'chat_id': chat_id, 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'application_id': application_id, 'ip_address': ip_address, 'source': source, }) open_chat.is_valid(raise_exception=True) chat_info = open_chat.re_open_chat(chat_id) chat_info.set_cache() return chat_id def chat(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) OpenAIInstanceSerializer(data=instance).is_valid(raise_exception=True) chat_id = instance.get('chat_id') message = self.get_message(instance) re_chat = instance.get('re_chat', False) stream = instance.get('stream', False) application_id = self.data.get('application_id') chat_user_id = self.data.get('chat_user_id') chat_user_type = self.data.get('chat_user_type') ip_address = self.data.get('ip_address') source = self.data.get('source') chat_id = self.generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type, ip_address, source) return ChatSerializers( data={ 'chat_id': chat_id, 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'application_id': application_id, 'ip_address': ip_address, 'source': source, } ).chat({'message': message, 're_chat': re_chat, 'stream': stream, 'form_data': instance.get('form_data', {}), 'image_list': instance.get('image_list', []), 'document_list': instance.get('document_list', []), 'audio_list': instance.get('audio_list', []), 'other_list': instance.get('other_list', [])}, base_to_response=OpenaiToResponse()) class ChatSerializers(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) chat_user_id = serializers.CharField(required=True, label=_("Client id")) chat_user_type = serializers.CharField(required=True, label=_("Client Type")) application_id = serializers.UUIDField(required=True, allow_null=True, label=_("Application ID")) debug = serializers.BooleanField(required=False, label=_("Debug")) ip_address = serializers.CharField(required=False, label=_("IP Address"), allow_null=True, allow_blank=True) source = serializers.JSONField(required=False, label=_("Source")) def is_valid_application_workflow(self, *, raise_exception=False): self.is_valid_intraday_access_num() def is_valid_chat_id(self, chat_info: ChatInfo): if self.data.get('application_id') is not None and self.data.get('application_id') != str( chat_info.application_id): raise ChatException(500, _("Conversation does not exist")) def is_valid_intraday_access_num(self): if not self.data.get('debug') and [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__( self.data.get('chat_user_type')): access_client = QuerySet(ApplicationChatUserStats).filter(chat_user_id=self.data.get('chat_user_id'), application_id=self.data.get( 'application_id')).first() if access_client is None: access_client = ApplicationChatUserStats(chat_user_id=self.data.get('chat_user_id'), chat_user_type=self.data.get('chat_user_type'), application_id=self.data.get('application_id'), access_num=0, intraday_access_num=0) access_client.save() application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=self.data.get('application_id')).first() if application_access_token.access_num <= access_client.intraday_access_num: raise AppChatNumOutOfBoundsFailed(1002, _("The number of visits exceeds today's visits")) def is_valid_application_simple(self, *, chat_info: ChatInfo, raise_exception=False): self.is_valid_intraday_access_num() model_id = chat_info.application.model_id if model_id is None: return chat_info model = QuerySet(Model).filter(id=model_id).first() if model is None: return chat_info if model.status == Status.ERROR: raise ChatException(500, _("The current model is not available")) if model.status == Status.DOWNLOAD: raise ChatException(500, _("The model is downloading, please try again later")) return chat_info def chat_simple(self, chat_info: ChatInfo, instance, base_to_response): message = instance.get('message') re_chat = instance.get('re_chat') stream = instance.get('stream') chat_user_id = self.data.get('chat_user_id') chat_user_type = self.data.get('chat_user_type') ip_address = self.data.get('ip_address') source = self.data.get('source') form_data = instance.get("form_data") chat_record_id = instance.get('chat_record_id') pipeline_manage_builder = PipelineManage.builder() # 如果开启了问题优化,则添加上问题优化步骤 if chat_info.application.problem_optimization: pipeline_manage_builder.append_step(BaseResetProblemStep) # 构建流水线管理器 pipeline_message = (pipeline_manage_builder.append_step(BaseSearchDatasetStep) .append_step(BaseGenerateHumanMessageStep) .append_step(BaseChatStep) .add_base_to_response(base_to_response) .add_debug(self.data.get('debug', False)) .build()) exclude_paragraph_id_list = [] # 相同问题是否需要排除已经查询到的段落 if re_chat: paragraph_id_list = flat_map( [[paragraph.get('id') for paragraph in chat_record.details['search_step']['paragraph_list']] for chat_record in chat_info.chat_record_list if chat_record.problem_text == message and 'search_step' in chat_record.details and 'paragraph_list' in chat_record.details['search_step']]) exclude_paragraph_id_list = list(set(paragraph_id_list)) # 构建运行参数 params = chat_info.to_pipeline_manage_params(message, get_post_handler(chat_info), exclude_paragraph_id_list, chat_user_id, chat_user_type, ip_address, source, stream, form_data) if chat_record_id: params['chat_record_id'] = chat_record_id chat_info.set_chat(message) # 运行流水线作业 pipeline_message.run(params) return pipeline_message.context['chat_result'] @staticmethod def get_chat_record(chat_info, chat_record_id): if chat_info is not None: chat_record_list = [chat_record for chat_record in chat_info.chat_record_list if str(chat_record.id) == str(chat_record_id)] if chat_record_list is not None and len(chat_record_list): return chat_record_list[-1] chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() if chat_record is None: if not is_valid_uuid(chat_record_id): raise ChatException(500, _("Conversation record does not exist")) chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first() return chat_record def chat_work_flow(self, chat_info: ChatInfo, instance: dict, base_to_response): message = instance.get('message') re_chat = instance.get('re_chat') stream = instance.get('stream') chat_user_id = self.data.get("chat_user_id") chat_user_type = self.data.get('chat_user_type') ip_address = self.data.get('ip_address') source = self.data.get('source') form_data = instance.get('form_data') image_list = instance.get('image_list') video_list = instance.get('video_list') document_list = instance.get('document_list') audio_list = instance.get('audio_list') other_list = instance.get('other_list') workspace_id = chat_info.application.workspace_id chat_record_id = instance.get('chat_record_id') debug = self.data.get('debug', False) chat_record = None history_chat_record = chat_info.chat_record_list if chat_record_id is not None: chat_record = self.get_chat_record(chat_info, chat_record_id) if chat_record: history_chat_record = [r for r in chat_info.chat_record_list if str(r.id) != chat_record_id] work_flow = chat_info.application.work_flow work_flow_manage = WorkflowManage(Workflow.new_instance(work_flow), {'history_chat_record': history_chat_record, 'question': message, 'chat_id': chat_info.chat_id, 'chat_record_id': str( uuid.uuid7()) if chat_record_id is None else str(chat_record_id), 'stream': stream, 're_chat': re_chat, 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'ip_address': ip_address, 'source': source, 'workspace_id': workspace_id, 'debug': debug, 'chat_user': chat_info.get_chat_user(), 'chat_user_group': chat_info.get_chat_user_group(), 'application_id': str(chat_info.application_id)}, WorkFlowPostHandler(chat_info), base_to_response, form_data, image_list, document_list, audio_list, video_list, other_list, instance.get('runtime_node_id'), instance.get('node_data'), chat_record, instance.get('child_node')) chat_info.set_chat(message) r = work_flow_manage.run() return r def is_valid_chat_user(self): chat_user_id = self.data.get('chat_user_id') application_id = self.data.get('application_id') chat_user_type = self.data.get('chat_user_type') is_auth_chat_user = DatabaseModelManage.get_model("is_auth_chat_user") application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application_id).first() if application_access_token and application_access_token.authentication and application_access_token.authentication_value.get( 'type') == 'login': if chat_user_type == ChatUserType.CHAT_USER.value and is_auth_chat_user: is_auth = is_auth_chat_user(chat_user_id, application_id) if not is_auth: raise ChatException(500, _("The chat user is not authorized.")) def chat(self, instance: dict, base_to_response: BaseToResponse = SystemToResponse()): super().is_valid(raise_exception=True) ChatMessageSerializers(data=instance).is_valid(raise_exception=True) chat_info = self.get_chat_info() chat_info.get_application() chat_info.get_chat_user(asker=(instance.get('form_data') or {}).get('asker')) self.is_valid_chat_id(chat_info) self.is_valid_chat_user() if chat_info.application.type == ApplicationTypeChoices.SIMPLE: self.is_valid_application_simple(raise_exception=True, chat_info=chat_info) return self.chat_simple(chat_info, instance, base_to_response) else: self.is_valid_application_workflow(raise_exception=True) return self.chat_work_flow(chat_info, instance, base_to_response) def get_chat_info(self): self.is_valid(raise_exception=True) chat_id = self.data.get('chat_id') chat_info: ChatInfo = ChatInfo.get_cache(chat_id) if chat_info is None: chat_info: ChatInfo = self.re_open_chat(chat_id) chat_info.set_cache() return chat_info def re_open_chat(self, chat_id: str): chat = QuerySet(Chat).filter(id=chat_id).first() if chat is None: raise ChatException(500, _("Conversation does not exist")) application = QuerySet(Application).filter(id=chat.application_id).first() if application is None: raise ChatException(500, _("Application does not exist")) application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by( '-create_time')[0:1].first() if application_version is None: raise ChatException(500, _("The application has not been published. Please use it after publishing.")) if application.type == ApplicationTypeChoices.SIMPLE: return self.re_open_chat_simple(chat_id, application) else: return self.re_open_chat_work_flow(chat_id, application) def re_open_chat_simple(self, chat_id, application): # 数据集id列表 knowledge_id_list = [str(row.target_id) for row in QuerySet(ResourceMapping).filter(source_id=str(application.id), source_type='APPLICATION', target_type='KNOWLEDGE')] # 需要排除的文档 exclude_document_id_list = [str(document.id) for document in QuerySet(Document).filter( knowledge_id__in=knowledge_id_list, is_active=False)] chat_info = ChatInfo(chat_id, self.data.get('chat_user_id'), self.data.get('chat_user_type'), self.data.get('ip_address'), self.data.get('source'), knowledge_id_list, exclude_document_id_list, application.id) chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5]) chat_record_list.sort(key=lambda r: r.create_time) for chat_record in chat_record_list: chat_info.chat_record_list.append(chat_record) return chat_info def re_open_chat_work_flow(self, chat_id, application): chat_info = ChatInfo(chat_id, self.data.get('chat_user_id'), self.data.get('chat_user_type'), self.data.get('ip_address'), self.data.get('source'), [], [], application.id) chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5]) chat_record_list.sort(key=lambda r: r.create_time) for chat_record in chat_record_list: chat_info.chat_record_list.append(chat_record) return chat_info class OpenChatSerializers(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True) chat_user_id = serializers.CharField(required=True, label=_("Client id")) chat_user_type = serializers.CharField(required=True, label=_("Client Type")) debug = serializers.BooleanField(required=True, label=_("Debug")) ip_address = serializers.CharField(required=False, label=_("IP Address")) source = serializers.JSONField(required=False, label=_("Source")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') application_id = self.data.get('application_id') query_set = QuerySet(Application).filter(id=application_id) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, gettext('Application does not exist')) def open(self): self.is_valid(raise_exception=True) application_id = self.data.get('application_id') application = QuerySet(Application).get(id=application_id) debug = self.data.get("debug") if not debug: application_version = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by( '-create_time')[0:1].first() if application_version is None: raise AppApiException(500, _("The application has not been published. Please use it after publishing.")) if application.type == ApplicationTypeChoices.SIMPLE: return self.open_simple(application) else: return self.open_work_flow(application) def open_work_flow(self, application): self.is_valid(raise_exception=True) application_id = self.data.get('application_id') chat_user_id = self.data.get("chat_user_id") chat_user_type = self.data.get("chat_user_type") ip_address = self.data.get("ip_address") source = self.data.get("source") debug = self.data.get("debug") chat_id = str(uuid.uuid7()) ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, [], [], application_id, debug).set_cache() return chat_id def open_simple(self, application): application_id = self.data.get('application_id') chat_user_id = self.data.get("chat_user_id") chat_user_type = self.data.get("chat_user_type") ip_address = self.data.get("ip_address") source = self.data.get("source") debug = self.data.get("debug") knowledge_id_list = [str(row.target_id) for row in QuerySet(ResourceMapping).filter(source_id=str(application_id), source_type='APPLICATION', target_type='KNOWLEDGE')] chat_id = str(uuid.uuid7()) ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, knowledge_id_list, [str(document.id) for document in QuerySet(Document).filter( knowledge_id__in=knowledge_id_list, is_active=False)], application_id, debug=debug).set_cache() return chat_id class TextToSpeechSerializers(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) def text_to_speech(self, instance): self.is_valid(raise_exception=True) application_id = self.data.get('application_id') application = QuerySet(Application).filter(id=application_id).first() return ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': application.user_id}).text_to_speech(instance, False) class SpeechToTextSerializers(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) def speech_to_text(self, instance): self.is_valid(raise_exception=True) application_id = self.data.get('application_id') application = QuerySet(Application).filter(id=application_id).first() return ApplicationOperateSerializer( data={'application_id': application_id, 'user_id': application.user_id}).speech_to_text(instance, False) ================================================ FILE: apps/chat/serializers/chat_authentication.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: ChatAuthentication.py @date:2025/6/6 13:48 @desc: """ import uuid_utils.compat as uuid from django.core import signing from django.core.cache import cache from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion from application.serializers.application import ApplicationSerializerModel from common.auth.common import ChatUserToken, ChatAuthentication from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import NotFound404, AppUnauthorizedFailed from common.utils.rsa_util import get_key_pair_by_sql class AnonymousAuthenticationSerializer(serializers.Serializer): access_token = serializers.CharField(required=True, label=_("access_token")) def auth(self, request, with_valid=True): token = request.META.get('HTTP_AUTHORIZATION') token_details = {} try: # 校验token if token is not None: token_details = signing.loads(token[7:]) except Exception as e: pass if with_valid: self.is_valid(raise_exception=True) access_token = self.data.get("access_token") application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first() if application_access_token is not None and application_access_token.is_active: chat_user_id = token_details.get('chat_user_id') or str(uuid.uuid7()) _type = AuthenticationType.CHAT_ANONYMOUS_USER return ChatUserToken(application_access_token.application_id, None, access_token, _type, ChatUserType.ANONYMOUS_USER, chat_user_id, ChatAuthentication(None)).to_token() else: raise NotFound404(404, _("Invalid access_token")) class AuthProfileSerializer(serializers.Serializer): access_token = serializers.CharField(required=True, label=_("access_token")) def profile(self): self.is_valid(raise_exception=True) access_token = self.data.get("access_token") application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first() if application_access_token is None: raise NotFound404(404, _("Invalid access_token")) if not application_access_token.is_active: raise NotFound404(404, _("Invalid access_token")) application_id = application_access_token.application_id profile = { 'authentication': False } application_setting_model = DatabaseModelManage.get_model('application_setting') chat_platform = DatabaseModelManage.get_model('chat_platform') if application_setting_model and chat_platform: application_setting = QuerySet(application_setting_model).filter(application_id=application_id).first() types = QuerySet(chat_platform).filter(is_active=True, is_valid=True).values_list('auth_type', flat=True) login_value = application_access_token.authentication_value.get('login_value', []) max_attempts = application_access_token.authentication_value.get('max_attempts', 1) final_login_value = list(set(login_value) & set(types)) if 'LOCAL' in login_value: final_login_value.insert(0, 'LOCAL') if application_setting is not None: profile = { 'icon': application_setting.application.icon, 'application_name': application_setting.application.name, 'bg_icon': application_setting.chat_background, 'authentication': application_access_token.authentication, 'authentication_type': application_access_token.authentication_value.get( 'type', 'password'), 'max_attempts': max_attempts, 'login_value': final_login_value, 'rasKey' : get_key_pair_by_sql().get('key') } return profile class ApplicationProfileSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) @staticmethod def reset_application(application, application_version): update_field_dict = { 'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number', 'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting', 'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting', 'tts_model_params_setting': 'tts_model_params_setting', 'problem_optimization': 'problem_optimization', 'work_flow': 'work_flow', 'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id', 'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable', 'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type', 'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable', 'file_upload_setting': 'file_upload_setting' } for (version_field, app_field) in update_field_dict.items(): _v = getattr(application_version, version_field) setattr(application, app_field, _v) def profile(self, with_valid=True): if with_valid: self.is_valid() application_id = self.data.get("application_id") application = QuerySet(Application).get(id=application_id) application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application.id).first() if application_access_token is None: raise AppUnauthorizedFailed(500, _("Illegal User")) application_setting_model = DatabaseModelManage.get_model('application_setting') application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by( '-create_time').first() if application_version is not None: self.reset_application(application, application_version) license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'), version=Cache_Version.SYSTEM.get_version()) application_setting_dict = {} if application_setting_model is not None and license_is_valid: application_setting = QuerySet(application_setting_model).filter( application_id=application_access_token.application_id).first() if application_setting is not None: custom_theme = getattr(application_setting, 'custom_theme', {}) float_location = getattr(application_setting, 'float_location', {}) if not custom_theme: application_setting.custom_theme = { 'theme_color': '', 'header_font_color': '' } if not float_location: application_setting.float_location = { 'x': {'type': '', 'value': ''}, 'y': {'type': '', 'value': ''} } application_setting_dict = {'show_source': application_access_token.show_source, 'show_history': application_setting.show_history, 'draggable': application_setting.draggable, 'show_guide': application_setting.show_guide, 'avatar': application_setting.avatar, 'show_avatar': application_setting.show_avatar, 'float_icon': application_setting.float_icon, 'disclaimer': application_setting.disclaimer, 'disclaimer_value': application_setting.disclaimer_value, 'custom_theme': application_setting.custom_theme, 'user_avatar': application_setting.user_avatar, 'show_user_avatar': application_setting.show_user_avatar, 'float_location': application_setting.float_location, 'chat_background': application_setting.chat_background} base_node = [node for node in ((application.work_flow or {}).get('nodes', []) or []) if node.get('id') == 'base-node'] return {**ApplicationSerializerModel(application).data, 'stt_model_id': application.stt_model_id, 'tts_model_id': application.tts_model_id, 'stt_model_enable': application.stt_model_enable, 'tts_model_enable': application.tts_model_enable, 'tts_type': application.tts_type, 'tts_autoplay': application.tts_autoplay, 'stt_autosend': application.stt_autosend, 'file_upload_enable': application.file_upload_enable, 'file_upload_setting': application.file_upload_setting, 'work_flow': {'nodes': base_node} if base_node else None, 'show_source': application_access_token.show_source, 'show_exec': application_access_token.show_exec, 'language': application_access_token.language, **application_setting_dict} ================================================ FILE: apps/chat/serializers/chat_embed_serializers.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_embed_serializers.py @date:2025/5/30 14:34 @desc: """ import os import uuid_utils.compat as uuid from django.db.models import QuerySet from django.http import HttpResponse from django.template import Template, Context from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ApplicationAccessToken from common.database_model_manage.database_model_manage import DatabaseModelManage from maxkb.conf import PROJECT_DIR from maxkb.const import CONFIG class ChatEmbedSerializer(serializers.Serializer): host = serializers.CharField(required=True, label=_("Host")) protocol = serializers.CharField(required=True, label=_("protocol")) token = serializers.CharField(required=True, label=_("token")) def get_embed(self, with_valid=True, params=None): if params is None: params = {} if with_valid: self.is_valid(raise_exception=True) index_path = os.path.join(PROJECT_DIR, 'apps', "chat", 'template', 'embed.js') file = open(index_path, "r", encoding='utf-8') content = file.read() file.close() application_access_token = QuerySet(ApplicationAccessToken).filter( access_token=self.data.get('token')).first() is_draggable = 'false' show_guide = 'true' float_icon = f"{self.data.get('protocol')}://{self.data.get('host')}{CONFIG.get_chat_path()}/MaxKB.gif" is_license_valid = DatabaseModelManage.get_model('license_is_valid') X_PACK_LICENSE_IS_VALID = is_license_valid() if is_license_valid is not None else False # 获取接入的query参数 query = self.get_query_api_input(application_access_token.application, params) float_location = {"x": {"type": "right", "value": 0}, "y": {"type": "bottom", "value": 30}} header_font_color = "rgb(100, 106, 115)" application_setting_model = DatabaseModelManage.get_model('application_setting') if application_setting_model is not None and X_PACK_LICENSE_IS_VALID: application_setting = QuerySet(application_setting_model).filter( application_id=application_access_token.application_id).first() if application_setting is not None: is_draggable = 'true' if application_setting.draggable else 'false' if application_setting.float_icon is not None and len(application_setting.float_icon) > 0: float_icon = application_setting.float_icon[1:] if application_setting.float_icon.startswith( '.') else application_setting.float_icon float_icon = f"{self.data.get('protocol')}://{self.data.get('host')}{CONFIG.get_chat_path()}{float_icon}" show_guide = 'true' if application_setting.show_guide else 'false' if application_setting.float_location is not None: float_location = application_setting.float_location if application_setting.custom_theme is not None and len( application_setting.custom_theme.get('header_font_color', 'rgb(100, 106, 115)')) > 0: header_font_color = application_setting.custom_theme.get('header_font_color', 'rgb(100, 106, 115)') is_auth = 'true' if application_access_token is not None and application_access_token.is_active else 'false' t = Template(content) s = t.render( Context( {'is_auth': is_auth, 'protocol': self.data.get('protocol'), 'host': self.data.get('host'), 'token': self.data.get('token'), 'white_list_str': ",".join( application_access_token.white_list if application_access_token.white_list is not None else []), 'white_active': 'true' if application_access_token.white_active else 'false', 'is_draggable': is_draggable, 'float_icon': float_icon, 'prefix': CONFIG.get_chat_path(), 'query': query, 'show_guide': show_guide, 'x_type': float_location.get('x', {}).get('type', 'right'), 'x_value': float_location.get('x', {}).get('value', 0), 'y_type': float_location.get('y', {}).get('type', 'bottom'), 'y_value': float_location.get('y', {}).get('value', 30), 'max_kb_id': str(uuid.uuid7()).replace('-', ''), 'header_font_color': header_font_color})) response = HttpResponse(s, status=200, headers={'Content-Type': 'text/javascript'}) return response @staticmethod def get_query_api_input(application, params): query = '' if application.work_flow is not None: work_flow = application.work_flow if work_flow is not None: for node in work_flow.get('nodes', []): if node['id'] == 'base-node': input_field_list = node.get('properties', {}).get('api_input_field_list', node.get('properties', {}).get( 'input_field_list', [])) if input_field_list is not None: for field in input_field_list: if field['assignment_method'] == 'api_input' and field['variable'] in params: query += f"&{field['variable']}={params[field['variable']]}" if 'asker' in params: query += f"&asker={params.get('asker')}" return query ================================================ FILE: apps/chat/serializers/chat_record.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_record.py @date:2025/6/23 11:16 @desc: """ from typing import Dict from django.db import transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _, gettext from rest_framework import serializers from application.models import VoteChoices, ChatRecord, Chat, ApplicationAccessToken, VoteReasonChoices from application.serializers.application_chat import ChatCountSerializer from application.serializers.application_chat_record import ChatRecordSerializerModel, \ ApplicationChatRecordQuerySerializers from common.db.search import page_search from common.exception.app_exception import AppApiException from common.utils.lock import RedisLock class VoteRequest(serializers.Serializer): vote_status = serializers.ChoiceField(choices=VoteChoices.choices, label=_("Bidding Status")) vote_reason = serializers.ChoiceField(choices=VoteReasonChoices.choices, label=_("Vote Reason"), required=False, allow_null=True) vote_other_content = serializers.CharField(required=False, allow_blank=True, label=_("Vote other content")) class HistoryChatModel(serializers.ModelSerializer): class Meta: model = Chat fields = ['id', 'application_id', 'abstract', 'create_time', 'update_time'] class VoteSerializer(serializers.Serializer): chat_id = serializers.UUIDField(required=True, label=_("Conversation ID")) chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id")) @transaction.atomic def vote(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) VoteRequest(data=instance).is_valid(raise_exception=True) rlock = RedisLock() if not rlock.try_lock(self.data.get('chat_record_id')): raise AppApiException(500, gettext( "Voting on the current session minutes, please do not send repeated requests")) try: chat_record_details_model = QuerySet(ChatRecord).get(id=self.data.get('chat_record_id'), chat_id=self.data.get('chat_id')) if chat_record_details_model is None: raise AppApiException(500, gettext("Non-existent conversation chat_record_id")) vote_status = instance.get("vote_status") # 未投票状态,可以进行投票 if chat_record_details_model.vote_status == VoteChoices.UN_VOTE: # 投票时获取字段 vote_reason = instance.get("vote_reason") vote_other_content = instance.get("vote_other_content") or '' if vote_status == VoteChoices.STAR: # 点赞 chat_record_details_model.vote_status = VoteChoices.STAR chat_record_details_model.vote_reason = vote_reason chat_record_details_model.vote_other_content = vote_other_content if vote_status == VoteChoices.TRAMPLE: # 点踩 chat_record_details_model.vote_status = VoteChoices.TRAMPLE chat_record_details_model.vote_reason = vote_reason chat_record_details_model.vote_other_content = vote_other_content chat_record_details_model.save() # 已投票状态 else: if vote_status == VoteChoices.UN_VOTE: # 取消点赞 chat_record_details_model.vote_status = VoteChoices.UN_VOTE chat_record_details_model.vote_reason = None chat_record_details_model.vote_other_content = '' chat_record_details_model.save() else: raise AppApiException(500, gettext("Already voted, please cancel first and then vote again")) finally: rlock.un_lock(self.data.get('chat_record_id')) ChatCountSerializer(data={'chat_id': self.data.get('chat_id')}).update_chat() return True class HistoricalConversationSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_('Application ID')) chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID')) def get_queryset(self): chat_user_id = self.data.get('chat_user_id') application_id = self.data.get("application_id") return QuerySet(Chat).filter(application_id=application_id, chat_user_id=chat_user_id, is_deleted=False).order_by('-update_time', 'id') def list(self): self.is_valid(raise_exception=True) queryset = self.get_queryset() return [HistoryChatModel(r).data for r in queryset] def page(self, current_page, page_size): self.is_valid(raise_exception=True) return page_search(current_page, page_size, self.get_queryset(), lambda r: HistoryChatModel(r).data) class EditAbstractSerializer(serializers.Serializer): abstract = serializers.CharField(required=True, label=_('Abstract')) class HistoricalConversationOperateSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_('Application ID')) chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID')) chat_id = serializers.UUIDField(required=True, label=_('Chat ID')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) e = QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'), chat_user_id=self.data.get('chat_user_id')).exists() if not e: raise AppApiException(500, _('Chat is not exist')) def edit_abstract(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EditAbstractSerializer(data=instance).is_valid(raise_exception=True) QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'), chat_user_id=self.data.get('chat_user_id')).update(abstract=instance.get('abstract')) return True def logic_delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'), chat_user_id=self.data.get('chat_user_id')).update(is_deleted=True) return True class Clear(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_('Application ID')) chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID')) def batch_logic_delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) QuerySet(Chat).filter(application_id=self.data.get('application_id'), chat_user_id=self.data.get('chat_user_id')).update(is_deleted=True) return True class HistoricalConversationRecordSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_('Application ID')) chat_id = serializers.UUIDField(required=True, label=_('Chat ID')) chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) chat_user_id = self.data.get('chat_user_id') application_id = self.data.get("application_id") chat_id = self.data.get('chat_id') chat_exist = QuerySet(Chat).filter(application_id=application_id, chat_user_id=chat_user_id, id=chat_id).exists() if not chat_exist: raise AppApiException(500, _('Non-existent chatID')) def get_queryset(self): chat_id = self.data.get('chat_id') return QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time') def list(self): self.is_valid(raise_exception=True) queryset = self.get_queryset() return [ChatRecordSerializerModel(r).data for r in queryset] def page(self, current_page, page_size): self.is_valid(raise_exception=True) application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=self.data.get('application_id')).first() show_source = False show_exec = False if application_access_token is not None: show_exec = application_access_token.show_exec show_source = application_access_token.show_source return ApplicationChatRecordQuerySerializers( data={'application_id': self.data.get('application_id'), 'chat_id': self.data.get('chat_id')}).page( current_page, page_size, show_source=show_source, show_exec=show_exec) ================================================ FILE: apps/chat/template/embed.js ================================================ (function() { const guideHtml=`
🌟 遇见问题,不再有障碍!

你好,我是你的智能小助手。
点我,开启高效解答模式,让问题变成过去式。

` const chatButtonHtml= `
` const getChatContainerHtml=(protocol,host,token,query,prefix)=>{ return `
` } /** * 初始化引导 * @param {*} root */ const initGuide=(root)=>{ root.insertAdjacentHTML("beforeend",guideHtml) const button=root.querySelector(".maxkb-button") const close_icon=root.querySelector('.maxkb-close') const close_func=()=>{ root.removeChild(root.querySelector('.maxkb-tips')) root.removeChild(root.querySelector('.maxkb-mask')) localStorage.setItem('maxkbMaskTip',true) } button.onclick=close_func close_icon.onclick=close_func } const initChat=(root)=>{ // 添加对话icon root.insertAdjacentHTML("beforeend",chatButtonHtml) // 添加对话框 root.insertAdjacentHTML('beforeend',getChatContainerHtml('{{protocol}}','{{host}}','{{token}}','{{query}}','{{prefix}}')) // 按钮元素 const chat_button=root.querySelector('.maxkb-chat-button') const chat_button_img=root.querySelector('.maxkb-chat-button > img') // 对话框元素 const chat_container=root.querySelector('#maxkb-chat-container') // 引导层 const mask_content = root.querySelector('.maxkb-mask > .maxkb-content') const mask_tips = root.querySelector('.maxkb-tips') chat_button_img.onload=(event)=>{ if(mask_content){ mask_content.style.width = chat_button_img.width + 'px' mask_content.style.height = chat_button_img.height + 'px' if('{{x_type}}'=='left'){ mask_tips.style.marginLeft = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px' }else{ mask_tips.style.marginRight = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px' } } } const viewport=root.querySelector('.maxkb-openviewport') const closeviewport=root.querySelector('.maxkb-closeviewport') const close_func=()=>{ chat_container.style['display']=chat_container.style['display']=='block'?'none':'block' chat_button.style['display']=chat_container.style['display']=='block'?'none':'block' } close_icon=chat_container.querySelector('.maxkb-chat-close') chat_button.onclick = close_func close_icon.onclick=close_func const viewport_func=()=>{ if(chat_container.classList.contains('maxkb-enlarge')){ chat_container.classList.remove("maxkb-enlarge"); viewport.classList.remove('maxkb-viewportnone') closeviewport.classList.add('maxkb-viewportnone') }else{ chat_container.classList.add("maxkb-enlarge"); viewport.classList.add('maxkb-viewportnone') closeviewport.classList.remove('maxkb-viewportnone') } } const drag=(e)=>{ if (['touchmove','touchstart'].includes(e.type)) { chat_button.style.top=(e.touches[0].clientY-chat_button_img.naturalHeight/2)+'px' chat_button.style.left=(e.touches[0].clientX-chat_button_img.naturalWidth/2)+'px' } else { chat_button.style.top=(e.y-chat_button_img.naturalHeight/2)+'px' chat_button.style.left=(e.x-chat_button_img.naturalWidth/2)+'px' } chat_button.style.width =chat_button_img.naturalWidth+'px' chat_button.style.height =chat_button_img.naturalHeight+'px' } if({{is_draggable}}){ chat_button.addEventListener("drag",drag) chat_button.addEventListener("dragover",(e)=>{ e.preventDefault() }) chat_button.addEventListener("dragend",drag) chat_button.addEventListener("touchstart",drag) chat_button.addEventListener("touchmove",drag) } viewport.onclick=viewport_func closeviewport.onclick=viewport_func } /** * 第一次进来的引导提示 */ function initMaxkb(){ const maxkb=document.createElement('div') const root=document.createElement('div') const maxkbId = 'maxkb-'+'{{max_kb_id}}' root.id=maxkbId initMaxkbStyle(maxkb, maxkbId) maxkb.appendChild(root) document.body.appendChild(maxkb) const maxkbMaskTip=localStorage.getItem('maxkbMaskTip') if(maxkbMaskTip==null && {{show_guide}}){ initGuide(root) } initChat(root) } // 初始化全局样式 function initMaxkbStyle(root, maxkbId){ style=document.createElement('style') style.type='text/css' style.innerText= ` /* 放大 */ #maxkb .maxkb-enlarge { width: 50%!important; height: 100%!important; bottom: 0!important; right: 0 !important; } @media only screen and (max-width: 768px){ #maxkb .maxkb-enlarge { width: 100%!important; height: 100%!important; right: 0 !important; bottom: 0!important; } } /* 引导 */ #maxkb .maxkb-mask { position: fixed; z-index: 10001; background-color: transparent; height: 100%; width: 100%; top: 0; left: 0; } #maxkb .maxkb-mask .maxkb-content { width: 64px; height: 64px; box-shadow: 1px 1px 1px 9999px rgba(0,0,0,.6); position: absolute; {{x_type}}: {{x_value}}px; {{y_type}}: {{y_value}}px; z-index: 10001; } #maxkb .maxkb-tips { position: fixed; {{x_type}}:calc({{x_value}}px + 75px); {{y_type}}: calc({{y_value}}px + 0px); padding: 22px 24px 24px; border-radius: 6px; color: #ffffff; font-size: 14px; background: #3370FF; z-index: 10001; } #maxkb .maxkb-tips .maxkb-arrow { position: absolute; background: #3370FF; width: 10px; height: 10px; pointer-events: none; transform: rotate(45deg); box-sizing: border-box; /* left */ {{x_type}}: -5px; {{y_type}}: 33px; border-left-color: transparent; border-bottom-color: transparent } #maxkb .maxkb-tips .maxkb-title { font-size: 20px; font-weight: 500; margin-bottom: 8px; } #maxkb .maxkb-tips .maxkb-button { text-align: right; margin-top: 24px; } #maxkb .maxkb-tips .maxkb-button button { border-radius: 4px; background: #FFF; padding: 3px 12px; color: #3370FF; cursor: pointer; outline: none; border: none; } #maxkb .maxkb-tips .maxkb-button button::after{ border: none; } #maxkb .maxkb-tips .maxkb-close { position: absolute; right: 20px; top: 20px; cursor: pointer; } #maxkb-chat-container { width: 460px; height: 680px; display:none; } @media only screen and (max-height: 680px) { #maxkb-chat-container{ height: 600px} } @media only screen and (max-width: 768px) { #maxkb-chat-container { width: 100%; height: 70%; right: 0 !important; } } #maxkb .maxkb-chat-button{ position: fixed; {{x_type}}: {{x_value}}px; {{y_type}}: {{y_value}}px; cursor: pointer; z-index:10000; } #maxkb #maxkb-chat-container{ z-index:10000;position: relative; border-radius: 8px; border: 1px solid #ffffff; background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1; box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10); position: fixed;bottom: 16px;right: 16px;overflow: hidden; } #maxkb #maxkb-chat-container .maxkb-operate{ top: 18px; right: 15px; position: absolute; display: flex; align-items: center; line-height: 18px; } #maxkb #maxkb-chat-container .maxkb-operate .maxkb-chat-close{ margin-left:15px; cursor: pointer; } #maxkb #maxkb-chat-container .maxkb-operate .maxkb-openviewport{ cursor: pointer; } #maxkb #maxkb-chat-container .maxkb-operate .maxkb-closeviewport{ cursor: pointer; } #maxkb #maxkb-chat-container .maxkb-viewportnone{ display:none; } #maxkb #maxkb-chat-container #maxkb-chat{ height:100%; width:100%; border: none; } #maxkb #maxkb-chat-container { animation: appear .4s ease-in-out; } @keyframes appear { from { height: 0;; } to { height: 600px; } }` .replaceAll('#maxkb ',`#${maxkbId} `) root.appendChild(style) } function embedChatbot() { white_list_str='{{white_list_str}}' white_list=white_list_str.split(',') if ({{is_auth}}&&({{white_active}}?white_list.includes(window.location.origin):true)) { // 初始化maxkb智能小助手 initMaxkb() } else console.error('invalid parameter') } window.addEventListener('load',embedChatbot) })(); ================================================ FILE: apps/chat/template/generate_prompt_system ================================================ ## 人设 你是一个专业的提示词生成优化专家,擅长为各种智能体应用场景创建高质量的系统角色设定。你具有深厚的AI应用理解能力和丰富的提示词工程经验。 ## 技能 1. 深度分析用户提供的智能体名称和功能描述 2. 根据应用场景生成结构化的系统角色设定 3. 优化提示词的逻辑结构和语言表达 4. 确保生成的角色设定具有清晰的人物设定、功能流程、约束限制和回复格式 当用户提供智能体信息时,你需要按照标准格式生成包含人物设定、功能和流程、约束与限制、回复格式四个核心模块的完整系统角色设定。 ## 限制 1. 严格按照人物设定、功能和流程、约束与限制、回复格式的结构输出 2. 不输出与角色设定生成无关的内容 3. 如果用户输入信息不够明确,基于智能体名称和已有描述进行合理推测 ## 回复格式 请严格按照以下格式输出: # 角色: 角色简短描述一句话 ## 目标: 角色的工作目标,如果有多目标可以分点列出,但建议更聚焦1-2个目标 ## 核心技能: ### 技能 1: [技能名称,如作品推荐/信息查询/专业分析等] 1. [执行步骤1 - 描述该技能的第一个具体操作步骤,包括条件判断和处理方式] 2. [执行步骤2 - 描述该技能的第二个具体操作步骤,包括如何获取或处理信息] 3. [执行步骤3 - 描述该技能的最终输出步骤,说明如何呈现结果] ===回复示例=== - 📋 [标识符]: <具体内容格式说明> - 🎯 [标识符]: <具体内容格式说明> - 💡 [标识符]: <具体内容格式说明> ===示例结束=== ### 技能 2: [技能名称] 1. [执行步骤1 - 描述触发条件和初始处理方式] 2. [执行步骤2 - 描述信息获取和深化处理的具体方法] 3. [执行步骤3 - 描述最终输出的具体要求和格式] ### 技能 3: [技能名称] - [核心能力描述 - 说明该技能的主要作用和知识基础] - [应用方法 - 描述如何运用该技能为用户提供服务,包括具体的实施方式] ## 工作流: 1. 描述角色工作流程的第一步 2. 描述角色工作流程的第二步 3. 描述角色工作流程的第三步 ## 输出格式: 如果对角色的输出格式有特定要求,可以在这里强调并举例说明想要的输出格式 ## 限制: 1. **严格限制回答范围**:仅回答与角色设定相关的问题。 - 如果用户提问与角色无关,必须使用以下固定格式回复: “对不起,我只能回答与[角色设定]相关的问题,您的问题不在服务范围内。” - 不得提供任何与角色设定无关的回答。 2. 描述角色在互动过程中需要遵循的限制条件2 3. 描述角色在互动过程中需要遵循的限制条件3 输出时不得包含任何解释或附加说明,只能返回符合以上格式的内容。 ================================================ FILE: apps/chat/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/chat/urls.py ================================================ from django.urls import path from application.views import ChatRecordDetailView, ChatRecordLinkView from chat.views.mcp import mcp_view from . import views app_name = 'chat' # @formatter:off urlpatterns = [ path('embed', views.ChatEmbedView.as_view()), path('mcp', mcp_view), path('auth/anonymous', views.AnonymousAuthentication.as_view()), path('profile', views.AuthProfile.as_view()), path('application/profile', views.ApplicationProfile.as_view(), name='profile'), path('chat_message/', views.ChatView.as_view(), name='chat'), path('open', views.OpenView.as_view(), name='open'), path('text_to_speech', views.TextToSpeech.as_view()), path('speech_to_text', views.SpeechToText.as_view()), path('captcha', views.CaptchaView.as_view(), name='captcha'), path('/chat/completions', views.OpenAIView.as_view(), name='application/chat_completions'), path('vote/chat//chat_record/', views.VoteView.as_view(), name='vote'), path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'), path('historical_conversation//record/',views.ChatRecordView.as_view(),name='conversation_details'), path('historical_conversation//', views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'), path('historical_conversation/clear',views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'), path('historical_conversation/',views.HistoricalConversationView.Operate.as_view(), name='historical_conversation_operate'), path('historical_conversation_record/', views.HistoricalConversationRecordView.as_view(), name='historical_conversation_record'), path('historical_conversation_record///', views.HistoricalConversationRecordView.PageView.as_view(), name='historical_conversation_record'), path('share/', ChatRecordDetailView.as_view()), path('/chat//share_chat', ChatRecordLinkView.as_view()), ] ================================================ FILE: apps/chat/views/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/5/29 16:08 @desc: """ from .chat_embed import * from .chat import * from .chat_record import * ================================================ FILE: apps/chat/views/chat.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat.py @date:2025/6/6 11:18 @desc: """ import requests from django.http import HttpResponse, StreamingHttpResponse from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_api import SpeechToTextAPI, TextToSpeechAPI from application.models import ChatUserType, ChatSourceChoices from chat.api.chat_api import ChatAPI from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \ TextToSpeechSerializers, OpenAIChatSerializer from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \ AuthProfileSerializer from common.auth import ChatTokenAuth from common.constants.permission_constants import ChatAuth from common.exception.app_exception import AppAuthenticationFailed from common.log.log import _get_ip_address from common.result import result from knowledge.models import FileSourceType from oss.serializers.file import FileSerializer from users.api import CaptchaAPI from users.serializers.login import CaptchaSerializer def stream_image(response): """生成器函数,用于流式传输图片数据""" for chunk in response.iter_content(chunk_size=4096): if chunk: # 过滤掉保持连接的空块 yield chunk class ResourceProxy(APIView): def get(self, request: Request): image_url = request.query_params.get("url") if not image_url: return result.error("Missing 'url' parameter") try: # 发送GET请求,流式获取图片内容 response = requests.get( image_url, stream=True, # 启用流式响应 allow_redirects=True, timeout=10 ) content_type = response.headers.get('Content-Type', '').split(';')[0] # 创建Django流式响应 django_response = StreamingHttpResponse( stream_image(response), # 使用生成器 content_type=content_type ) return django_response except Exception as e: return result.error(f"Image request failed: {str(e)}") class OpenAIView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['POST'], description=_('OpenAI Interface Dialogue'), summary=_('OpenAI Interface Dialogue'), operation_id=_('OpenAI Interface Dialogue'), # type: ignore request=OpenAIAPI.get_request(), responses=None, tags=[_('Chat')] # type: ignore ) def post(self, request: Request, application_id: str): ip_address = _get_ip_address(request) if application_id != str(request.auth.application_id): raise AppAuthenticationFailed(500, _('Secret key is invalid')) return OpenAIChatSerializer( data={'application_id': application_id, 'chat_user_id': request.auth.chat_user_id, 'chat_user_type': request.auth.chat_user_type, 'ip_address': ip_address, 'source': {"type": ChatSourceChoices.API_CALL.value}}).chat(request.data) class AnonymousAuthentication(APIView): def options(self, request, *args, **kwargs): return HttpResponse( headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}, ) @extend_schema( methods=['POST'], description=_('Application Anonymous Certification'), summary=_('Application Anonymous Certification'), operation_id=_('Application Anonymous Certification'), # type: ignore request=ChatAuthenticationAPI.get_request(), responses=None, tags=[_('Chat')] # type: ignore ) def post(self, request: Request): return result.success( AnonymousAuthenticationSerializer(data={'access_token': request.data.get("access_token")}).auth( request), headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"} ) class ApplicationProfile(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get application related information"), summary=_("Get application related information"), operation_id=_("Get application related information"), # type: ignore request=None, responses=None, tags=[_('Chat')] # type: ignore ) def get(self, request: Request): if isinstance(request.auth, ChatAuth): return result.success(ApplicationProfileSerializer( data={'application_id': request.auth.application_id}).profile()) raise AppAuthenticationFailed(401, "身份异常") class AuthProfile(APIView): @extend_schema( methods=['GET'], description=_("Get application authentication information"), summary=_("Get application authentication information"), operation_id=_("Get application authentication information"), # type: ignore parameters=ChatAuthenticationProfileAPI.get_parameters(), responses=None, tags=[_('Chat')] # type: ignore ) def get(self, request: Request): return result.success( AuthProfileSerializer(data={'access_token': request.query_params.get("access_token")}).profile()) class ChatView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['POST'], description=_("dialogue"), summary=_("dialogue"), operation_id=_("dialogue"), # type: ignore request=ChatAPI.get_request(), parameters=ChatAPI.get_parameters(), responses=None, tags=[_('Chat')] # type: ignore ) def post(self, request: Request, chat_id: str): ip_address = _get_ip_address(request) return ChatSerializers(data={'chat_id': chat_id, 'chat_user_id': request.auth.chat_user_id, 'chat_user_type': request.auth.chat_user_type, 'application_id': request.auth.application_id, 'debug': False, 'ip_address': ip_address, 'source': { 'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value} } ).chat(request.data) class OpenView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get the session id according to the application id"), summary=_("Get the session id according to the application id"), operation_id=_("Get the session id according to the application id"), # type: ignore parameters=ChatOpenAPI.get_parameters(), responses=None, tags=[_('Chat')] # type: ignore ) def get(self, request: Request): ip_address = _get_ip_address(request) return result.success(OpenChatSerializers( data={'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, 'chat_user_type': request.auth.chat_user_type, 'ip_address': ip_address, 'source': { 'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value}, 'debug': False}).open()) class CaptchaView(APIView): @extend_schema(methods=['GET'], summary=_("Get Chat captcha"), description=_("Get Chat captcha"), operation_id=_("Get Chat captcha"), # type: ignore tags=[_("Chat")], # type: ignore responses=CaptchaAPI.get_response()) def get(self, request: Request): username = request.query_params.get('username', None) accessToken = request.query_params.get('accessToken', None) return result.success(CaptchaSerializer().chat_generate(username, 'chat', accessToken)) class SpeechToText(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['POST'], description=_("speech to text"), summary=_("speech to text"), operation_id=_("speech to text"), # type: ignore request=SpeechToTextAPI.get_request(), responses=SpeechToTextAPI.get_response(), tags=[_('Chat')] # type: ignore ) def post(self, request: Request): return result.success( SpeechToTextSerializers( data={'application_id': request.auth.application_id}) .speech_to_text({'file': request.FILES.get('file')})) class TextToSpeech(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['POST'], description=_("text to speech"), summary=_("text to speech"), operation_id=_("text to speech"), # type: ignore request=TextToSpeechAPI.get_request(), responses=TextToSpeechAPI.get_response(), tags=[_('Chat')] # type: ignore ) def post(self, request: Request): byte_data = TextToSpeechSerializers( data={'application_id': request.auth.application_id}).text_to_speech(request.data) return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3', 'Content-Disposition': 'attachment; filename="abc.mp3"'}) class UploadFile(APIView): authentication_classes = [ChatTokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['POST'], description=_("Upload files"), summary=_("Upload files"), operation_id=_("Upload files"), # type: ignore request=TextToSpeechAPI.get_request(), responses=TextToSpeechAPI.get_response(), tags=[_('Application')] # type: ignore ) def post(self, request: Request, chat_id: str): files = request.FILES.getlist('file') file_ids = [] meta = {} for file in files: file_url = FileSerializer( data={'file': file, 'meta': meta, 'source_id': chat_id, 'source_type': FileSourceType.CHAT, }).upload() file_ids.append({'name': file.name, 'url': file_url, 'file_id': file_url.split('/')[-1]}) return result.success(file_ids) ================================================ FILE: apps/chat/views/chat_embed.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_embed.py @date:2025/5/30 15:22 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from chat.api.chat_embed_api import ChatEmbedAPI from chat.serializers.chat_embed_serializers import ChatEmbedSerializer class ChatEmbedView(APIView): @extend_schema( methods=['GET'], description=_('Get embedded js'), summary=_('Get embedded js'), operation_id=_('Get embedded js'), # type: ignore parameters=ChatEmbedAPI.get_parameters(), responses=ChatEmbedAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request): return ChatEmbedSerializer( data={'protocol': request.query_params.get('protocol'), 'token': request.query_params.get('token'), 'host': request.query_params.get('host'), }).get_embed(params=request.query_params) ================================================ FILE: apps/chat/views/chat_record.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_record.py @date:2025/6/23 10:42 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.serializers.application_chat_record import ChatRecordOperateSerializer from chat.api.chat_api import HistoricalConversationAPI, PageHistoricalConversationAPI, \ PageHistoricalConversationRecordAPI, HistoricalConversationRecordAPI, HistoricalConversationOperateAPI from chat.api.vote_api import VoteAPI from chat.serializers.chat_record import VoteSerializer, HistoricalConversationSerializer, \ HistoricalConversationRecordSerializer, HistoricalConversationOperateSerializer from common import result from common.auth import ChatTokenAuth class VoteView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['PUT'], description=_("Like, Dislike"), summary=_("Like, Dislike"), operation_id=_("Like, Dislike"), # type: ignore parameters=VoteAPI.get_parameters(), request=VoteAPI.get_request(), responses=VoteAPI.get_response(), tags=[_('Chat')] # type: ignore ) def put(self, request: Request, chat_id: str, chat_record_id: str): return result.success(VoteSerializer( data={'chat_id': chat_id, 'chat_record_id': chat_record_id }).vote(request.data)) class HistoricalConversationView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get historical conversation"), summary=_("Get historical conversation"), operation_id=_("Get historical conversation"), # type: ignore parameters=HistoricalConversationAPI.get_parameters(), responses=HistoricalConversationAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request): return result.success(HistoricalConversationSerializer( data={ 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).list()) class Operate(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['PUT'], description=_("Modify conversation about"), summary=_("Modify conversation about"), operation_id=_("Modify conversation about"), # type: ignore parameters=HistoricalConversationOperateAPI.get_parameters(), request=HistoricalConversationOperateAPI.get_request(), responses=HistoricalConversationOperateAPI.get_response(), tags=[_('Chat')] # type: ignore ) def put(self, request: Request, chat_id: str): return result.success(HistoricalConversationOperateSerializer( data={ 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, 'chat_id': chat_id, }).edit_abstract(request.data) ) @extend_schema( methods=['DELETE'], description=_("Delete history conversation"), summary=_("Delete history conversation"), operation_id=_("Delete history conversation"), # type: ignore parameters=HistoricalConversationOperateAPI.get_parameters(), responses=HistoricalConversationOperateAPI.get_response(), tags=[_('Chat')] # type: ignore ) def delete(self, request: Request, chat_id: str): return result.success(HistoricalConversationOperateSerializer( data={ 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, 'chat_id': chat_id, }).logic_delete()) class BatchDelete(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['DELETE'], description=_("Batch delete history conversation"), summary=_("Batch delete history conversation"), operation_id=_("Batch delete history conversation"), # type: ignore parameters=HistoricalConversationOperateAPI.get_parameters(), responses=HistoricalConversationOperateAPI.get_response(), tags=[_('Chat')] # type: ignore ) def delete(self, request: Request): return result.success(HistoricalConversationOperateSerializer.Clear(data={ 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).batch_logic_delete()) class PageView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get historical conversation by page"), summary=_("Get historical conversation by page"), operation_id=_("Get historical conversation by page"), # type: ignore parameters=PageHistoricalConversationAPI.get_parameters(), responses=PageHistoricalConversationAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request, current_page: int, page_size: int): return result.success(HistoricalConversationSerializer( data={ 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).page(current_page, page_size)) class HistoricalConversationRecordView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get historical conversation records"), summary=_("Get historical conversation records"), operation_id=_("Get historical conversation records"), # type: ignore parameters=HistoricalConversationRecordAPI.get_parameters(), responses=HistoricalConversationRecordAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request, chat_id: str): return result.success(HistoricalConversationRecordSerializer( data={ 'chat_id': chat_id, 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).list()) class PageView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get historical conversation records by page "), summary=_("Get historical conversation records by page"), operation_id=_("Get historical conversation records by page"), # type: ignore parameters=PageHistoricalConversationRecordAPI.get_parameters(), responses=PageHistoricalConversationRecordAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request, chat_id: str, current_page: int, page_size: int): return result.success(HistoricalConversationRecordSerializer( data={ 'chat_id': chat_id, 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).page(current_page, page_size)) class ChatRecordView(APIView): authentication_classes = [ChatTokenAuth] @extend_schema( methods=['GET'], description=_("Get conversation details"), summary=_("Get conversation details"), operation_id=_("Get conversation details"), # type: ignore parameters=PageHistoricalConversationRecordAPI.get_parameters(), responses=PageHistoricalConversationRecordAPI.get_response(), tags=[_('Chat')] # type: ignore ) def get(self, request: Request, chat_id: str, chat_record_id: str): return result.success(ChatRecordOperateSerializer( data={ 'chat_id': chat_id, 'chat_record_id': chat_record_id, 'application_id': request.auth.application_id, 'chat_user_id': request.auth.chat_user_id, }).one(False)) ================================================ FILE: apps/chat/views/mcp.py ================================================ import json from django.http import JsonResponse, HttpResponse from django.views.decorators.csrf import csrf_exempt from chat.mcp.tools import MCPToolHandler @csrf_exempt def mcp_view(request): request_id = None try: data = json.loads(request.body) method = data.get("method") params = data.get("params", {}) request_id = data.get("id") if request_id is None: return HttpResponse(status=204) auth_header = request.headers.get("Authorization", "").replace("Bearer ", "") handler = MCPToolHandler(auth_header) # 路由方法 if method == "initialize": result = handler.initialize() elif method == "tools/list": result = handler.list_tools() elif method == "tools/call": result = handler.call_tool(params) else: return JsonResponse({ "jsonrpc": "2.0", "id": request_id, "error": { "code": -32601, "message": f"Method not found: {method}" } }) # 成功响应 return JsonResponse({ "jsonrpc": "2.0", "id": request_id, "result": result }) except Exception as e: return JsonResponse({ "jsonrpc": "2.0", "id": request_id, "error": { "code": -32603, "message": f"Internal error: {str(e)}" } }) ================================================ FILE: apps/common/__init__.py ================================================ ================================================ FILE: apps/common/auth/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/14 10:44 @desc: """ from .authenticate import * ================================================ FILE: apps/common/auth/authenticate.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎虎 @file: authenticate.py @date:2023/9/4 11:16 @desc: 认证类 """ from importlib import import_module from django.conf import settings from django.core import cache from django.core import signing from django.utils.translation import gettext_lazy as _ from drf_spectacular.extensions import OpenApiAuthenticationExtension from rest_framework.authentication import TokenAuthentication from common.exception.app_exception import AppAuthenticationFailed, AppEmbedIdentityFailed, AppChatNumOutOfBoundsFailed, \ AppApiException from common.utils.logger import maxkb_logger token_cache = cache.caches['default'] class AnonymousAuthentication(TokenAuthentication): def authenticate(self, request): return None, None class AnonymousAuthenticationScheme(OpenApiAuthenticationExtension): target_class = AnonymousAuthentication # 绑定到你的自定义认证类 name = "AnonymousAuth" # 自定义认证名称(显示在 Swagger UI 中) def get_security_definition(self, auto_schema): # 定义认证方式,这里假设匿名认证不需要凭证 return { } def get_security_requirement(self, auto_schema): # 返回安全要求(空字典表示无需认证) return {} def new_instance_by_class_path(class_path: str): parts = class_path.rpartition('.') package_path = parts[0] class_name = parts[2] module = import_module(package_path) HandlerClass = getattr(module, class_name) return HandlerClass() handles = [new_instance_by_class_path(class_path) for class_path in settings.AUTH_HANDLES] chat_handles = [new_instance_by_class_path(class_path) for class_path in settings.CHAT_AUTH_HANDLES] all_handles = handles + chat_handles class TokenDetails: token_details = None is_load = False def __init__(self, token: str): self.token = token def get_token_details(self): if self.token_details is None and not self.is_load: try: self.token_details = signing.loads(self.token) except Exception as e: self.is_load = True return self.token_details class TokenAuth(TokenAuthentication): keyword = "Bearer" # 重新 authenticate 方法,自定义认证规则 def authenticate(self, request): auth = request.META.get('HTTP_AUTHORIZATION') # 未认证 if auth is None: raise AppAuthenticationFailed(1003, _('Not logged in, please log in first')) if not auth.startswith("Bearer "): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) try: token = auth[7:] token_details = TokenDetails(token) for handle in handles: if handle.support(request, token, token_details.get_token_details): return handle.handle(request, token, token_details.get_token_details) raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e, AppApiException): raise e raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) class ChatTokenAuth(TokenAuthentication): keyword = "Bearer" # 重新 authenticate 方法,自定义认证规则 def authenticate(self, request): auth = request.META.get('HTTP_AUTHORIZATION') # 未认证 if auth is None: raise AppAuthenticationFailed(1003, _('Not logged in, please log in first')) if not auth.startswith("Bearer "): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) try: token = auth[7:] token_details = TokenDetails(token) for handle in chat_handles: if handle.support(request, token, token_details.get_token_details): return handle.handle(request, token, token_details.get_token_details) raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e, AppApiException): raise e raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) class AllTokenAuth(TokenAuthentication): keyword = "Bearer" # 重新 authenticate 方法,自定义认证规则 def authenticate(self, request): auth = request.META.get('HTTP_AUTHORIZATION') # 未认证 if auth is None: raise AppAuthenticationFailed(1003, _('Not logged in, please log in first')) if not auth.startswith("Bearer "): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) try: token = auth[7:] token_details = TokenDetails(token) for handle in all_handles: if handle.support(request, token, token_details.get_token_details): return handle.handle(request, token, token_details.get_token_details) raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e, AppApiException): raise e raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) class WebhookAuth(TokenAuthentication): keyword = "Bearer" # 重新 authenticate 方法,自定义认证规则 def authenticate(self, request): return None, {} ================================================ FILE: apps/common/auth/authentication.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: authentication.py @date:2025/4/15 20:12 @desc: """ from typing import List from django.utils.translation import gettext_lazy as _ from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants, \ Permission, Role from common.exception.app_exception import AppUnauthorizedFailed def exist_permissions_by_permission_constants(user_permission: List[PermissionConstants], permission_list: List[PermissionConstants]): """ 用户是否拥有 permission_list的权限 :param user_permission: 用户权限 :param permission_list: 需要的权限 :return: 是否拥有 """ return any(list(map(lambda up: permission_list.__contains__(up), user_permission))) def exist_role_by_role_constants(user_role: List[RoleConstants], role_list: List[RoleConstants]): """ 用户是否拥有这个角色 :param user_role: 用户角色 :param role_list: 需要拥有的角色 :return: 是否拥有 """ return any([True for role in role_list if user_role.__contains__(role.value.__str__())]) def exist_permissions_by_view_permission(user_role: List[RoleConstants], user_permission: List[PermissionConstants | object], permission: ViewPermission, request, **kwargs): """ 用户是否存在这些权限 :param request: :param user_role: 用户角色 :param user_permission: 用户权限 :param permission: 所属权限 :return: 是否存在 True False """ role_list = [user_r(request, kwargs) if callable(user_r) else user_r for user_r in permission.roleList] role_ok = any(list(map(lambda up: role_list.__contains__(up), user_role))) permission_list = [user_p(request, kwargs) if callable(user_p) else user_p for user_p in permission.permissionList ] permission_ok = any(list(map(lambda up: permission_list.__contains__(up), user_permission))) return role_ok | permission_ok if permission.compare == CompareConstants.OR else role_ok & permission_ok def exist_permissions(user_role: List[RoleConstants], user_permission: List[PermissionConstants], permission, request, **kwargs): if isinstance(permission, ViewPermission): return exist_permissions_by_view_permission(user_role, user_permission, permission, request, **kwargs) if isinstance(permission, RoleConstants): return exist_role_by_role_constants(user_role, [permission]) if isinstance(permission, PermissionConstants): return exist_permissions_by_permission_constants(user_permission, [permission]) if isinstance(permission, Permission): return user_permission.__contains__(permission) if isinstance(permission, Role): return user_role.__contains__(permission.__str__()) return False def exist(user_role: List[RoleConstants], user_permission: List[PermissionConstants], permission, request, **kwargs): if callable(permission): p = permission(request, kwargs) return exist_permissions(user_role, user_permission, p, request, **kwargs) return exist_permissions(user_role, user_permission, permission, request, **kwargs) def get_is_permissions(request, **kwargs): def is_permissions(*permission, compare=CompareConstants.OR): exit_list = list( map(lambda p: exist(request.auth.role_list, request.auth.permission_list, p, request, **kwargs), permission)) return any(exit_list) if compare == CompareConstants.OR else all(exit_list) return is_permissions def has_permissions(*permission, compare=CompareConstants.OR): """ 权限 role or permission :param compare: 比较符号 :param permission: 如果是角色 role:roleId :return: 权限装饰器函数,用于判断用户是否有权限访问当前接口 """ def inner(func): def run(view, request, **kwargs): exit_list = list( map(lambda p: exist(request.auth.role_list, request.auth.permission_list, p, request, **kwargs), permission)) # 判断是否有权限 if any(exit_list) if compare == CompareConstants.OR else all(exit_list): return func(view, request, **kwargs) raise AppUnauthorizedFailed(403, _('No permission to access')) return run return inner ================================================ FILE: apps/common/auth/common.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: common.py @date:2025/6/6 19:55 @desc: """ import hashlib import json import threading from django.core import signing, cache from common.constants.cache_version import Cache_Version from common.utils.rsa_util import encrypt, decrypt authentication_cache = cache.cache lock = threading.Lock() def _decrypt(authentication: str): cache_key = hashlib.sha256(authentication.encode()).hexdigest() result = authentication_cache.get(key=cache_key, version=Cache_Version.CHAT.value) if result is None: with lock: result = authentication_cache.get(cache_key, version=Cache_Version.CHAT.value) if result is None: result = decrypt(authentication) authentication_cache.set(cache_key, result, version=Cache_Version.CHAT.value, timeout=60 * 60 * 2) return result class ChatAuthentication: def __init__(self, auth_type: str | None, **kwargs): self.auth_type = auth_type for k, v in kwargs.items(): self.__setattr__(k, v) def to_dict(self): return self.__dict__ def to_string(self): value = json.dumps(self.to_dict()) authentication = encrypt(value) cache_key = hashlib.sha256(authentication.encode()).hexdigest() authentication_cache.set(cache_key, value, version=Cache_Version.CHAT.get_version(), timeout=60 * 60 * 2) return authentication @staticmethod def new_instance(authentication: str): auth = json.loads(_decrypt(authentication)) return ChatAuthentication(**auth) class ChatUserToken: def __init__(self, application_id, user_id, access_token, _type, chat_user_type, chat_user_id, authentication: ChatAuthentication): self.application_id = application_id self.user_id = user_id self.access_token = access_token self.type = _type self.chat_user_type = chat_user_type self.chat_user_id = chat_user_id self.authentication = authentication def to_dict(self): return { 'application_id': str(self.application_id), 'user_id': str(self.user_id), 'access_token': self.access_token, 'type': str(self.type.value), 'chat_user_type': str(self.chat_user_type), 'chat_user_id': str(self.chat_user_id), 'authentication': self.authentication.to_string() } def to_token(self): return signing.dumps(self.to_dict()) @staticmethod def new_instance(token_dict): return ChatUserToken(token_dict.get('application_id'), token_dict.get('user_id'), token_dict.get('access_token'), token_dict.get('type'), token_dict.get('chat_user_type'), token_dict.get('chat_user_id'), ChatAuthentication.new_instance(token_dict.get('authentication'))) ================================================ FILE: apps/common/auth/handle/__init__.py ================================================ ================================================ FILE: apps/common/auth/handle/auth_base_handle.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎虎 @file: authenticate.py @date:2024/3/14 03:02 @desc: 认证处理器 """ from abc import ABC, abstractmethod class AuthBaseHandle(ABC): @abstractmethod def support(self, request, token: str, get_token_details): pass @abstractmethod def handle(self, request, token: str, get_token_details): pass ================================================ FILE: apps/common/auth/handle/impl/__init__.py ================================================ ================================================ FILE: apps/common/auth/handle/impl/application_key.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_key.py @date:2025/7/10 03:02 @desc: 应用api key认证 """ from django.db.models import QuerySet from django.utils import timezone from django.utils.translation import gettext_lazy as _ from application.models import ApplicationApiKey, ChatUserType, ApplicationAccessToken from common.auth.handle.auth_base_handle import AuthBaseHandle from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ChatAuth from common.exception.app_exception import AppAuthenticationFailed class ApplicationKey(AuthBaseHandle): def handle(self, request, token: str, get_token_details): application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=token).first() if application_api_key is None: raise AppAuthenticationFailed(500, _('Secret key is invalid')) if not application_api_key.is_active: raise AppAuthenticationFailed(500, _('Secret key is invalid')) if application_api_key.is_permanent is False and application_api_key.expire_time < timezone.now(): raise AppAuthenticationFailed(500, _('Secret key is expired')) application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=application_api_key.application_id).first() if application_access_token is not None: if application_access_token.authentication: if application_access_token.authentication_value.get('type', 'password') != 'password': raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) return None, ChatAuth( current_role_list=[RoleConstants.CHAT_ANONYMOUS_USER], permission_list=[ Permission(group=Group.APPLICATION, operate=Operate.READ)], application_id=application_api_key.application_id, chat_user_id=str(application_api_key.id), chat_user_type=ChatUserType.APPLICATION_API_KEY.value) def support(self, request, token: str, get_token_details): return str(token).startswith("application-") or str(token).startswith('agent-') ================================================ FILE: apps/common/auth/handle/impl/chat_anonymous_user_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: chat_anonymous_user_token.py @date:2025/6/6 15:08 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from application.models import ApplicationAccessToken from common.auth.common import ChatUserToken from common.auth.handle.auth_base_handle import AuthBaseHandle from common.constants.authentication_type import AuthenticationType from common.constants.permission_constants import RoleConstants, Permission, Group, Operate, ChatAuth from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppAuthenticationFailed from maxkb.settings import edition class ChatAnonymousUserToken(AuthBaseHandle): def support(self, request, token: str, get_token_details): token_details = get_token_details() if token_details is None: return False return ( 'application_id' in token_details and 'access_token' in token_details and token_details.get('type') == AuthenticationType.CHAT_ANONYMOUS_USER.value) def handle(self, request, token: str, get_token_details): auth_details = get_token_details() chat_user_token = ChatUserToken.new_instance(auth_details) application_id = chat_user_token.application_id access_token = chat_user_token.access_token application_access_token = QuerySet(ApplicationAccessToken).filter( application_id=application_id).first() if application_access_token is None: raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) if not application_access_token.is_active: raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) if not application_access_token.access_token == access_token: raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) if application_access_token.authentication and ['PE', 'EE'].__contains__(edition): if chat_user_token.authentication.auth_type != application_access_token.authentication_value.get('type', ''): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) return None, ChatAuth( current_role_list=[RoleConstants.CHAT_ANONYMOUS_USER], permission_list=[ Permission(group=Group.APPLICATION, operate=Operate.USE)], application_id=application_access_token.application_id, chat_user_id=chat_user_token.chat_user_id, chat_user_type=chat_user_token.chat_user_type) ================================================ FILE: apps/common/auth/handle/impl/user_token.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: authenticate.py @date:2024/3/14 03:02 @desc: 用户认证 """ from functools import reduce from typing import List from django.core.cache 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.cache_version import Cache_Version from common.constants.permission_constants import Auth, PermissionConstants, ResourcePermissionGroup, \ get_permission_list_by_resource_group, ResourceAuthType, \ ResourcePermissionRole, get_default_role_permission_mapping_list, get_default_workspace_user_role_mapping_list, \ RoleConstants, ResourcePermission, Resource, WorkspaceGroup from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppAuthenticationFailed from common.utils.common import group_by from maxkb.const import CONFIG from system_manage.models.workspace_user_permission import WorkspaceUserResourcePermission from users.models import User permission_constants_dict = {p.value.__str__(): p for p in PermissionConstants} def get_permission(permission_id): """ 获取权限字符串 @param permission_id: 权限id @return: 权限字符串 """ if isinstance(permission_id, PermissionConstants): permission_id = permission_id.value return f"{permission_id}" def get_workspace_permission(permission_id, workspace_id, role=None): """ 获取工作空间权限字符串 @param permission_id: 权限id @param workspace_id: 工作空间id @param role: 角色 @return: """ if isinstance(permission_id, PermissionConstants): permission_id = permission_id.value if role and role.type == RoleConstants.WORKSPACE_MANAGE.value.__str__(): return [f"{permission_id}:/WORKSPACE/{workspace_id}:ROLE/{role.type}", f"{permission_id}:/WORKSPACE/{workspace_id}"] return [f"{permission_id}:/WORKSPACE/{workspace_id}"] def get_role_permission(role, workspace_id): """ 获取工作空间角色 @param role: 角色 @param workspace_id: 工作空间id @return: """ if isinstance(role, RoleConstants): role = role.value return f"{role}:/WORKSPACE/{workspace_id}" def get_workspace_permission_list(role_permission_mapping_dict, workspace_user_role_mapping_list, role_model_dict): """ 获取工作空间下所有的权限 @param role_permission_mapping_dict: 角色权限关联字典 @param workspace_user_role_mapping_list: 工作空间用户角色关联列表 @param role_model_dict: 角色字典 @return: 工作空间下的权限 """ workspace_permission_list = [ [get_workspace_permission(role_permission_mapping.permission_id, w_u_r.workspace_id, role_model_dict.get(w_u_r.role_id, None)) for role_permission_mapping in role_permission_mapping_dict.get(w_u_r.role_id, [])] for w_u_r in workspace_user_role_mapping_list] return reduce(lambda x, y: [*x, *y], reduce(lambda x, y: [*x, *y], workspace_permission_list, []), []) def get_workspace_resource_permission_list( workspace_user_resource_permission_list: List[WorkspaceUserResourcePermission], role_permission_mapping_dict, workspace_user_role_mapping_dict): """ @param workspace_user_resource_permission_list: 工作空间用户资源权限列表 @param role_permission_mapping_dict: 角色权限关联字典 key为role_id @param workspace_user_role_mapping_dict: 工作空间用户角色映射字典 key为role_id @return: 工作空间资源权限列表 """ resource_permission_list = [ get_workspace_resource_permission_list_by_workspace_user_permission(workspace_user_resource_permission, role_permission_mapping_dict, workspace_user_role_mapping_dict) for workspace_user_resource_permission in workspace_user_resource_permission_list] # 将二维数组扁平为一维 return reduce(lambda x, y: [*x, *y], resource_permission_list, []) def get_workspace_resource_permission_list_by_workspace_user_permission( workspace_user_resource_permission: WorkspaceUserResourcePermission, role_permission_mapping_dict, workspace_user_role_mapping_dict): """ @param workspace_user_resource_permission: 工作空间用户资源权限对象 @param role_permission_mapping_dict: 角色权限关联字典 key为role_id @param workspace_user_role_mapping_dict: 工作空间用户角色关联字典 key为role_id @return: 工作空间用户资源的权限列表 """ role_permission_mapping_list = [role_permission_mapping_dict.get(workspace_user_role_mapping.role_id, []) for workspace_user_role_mapping in workspace_user_role_mapping_dict.get( workspace_user_resource_permission.workspace_id)] role_permission_mapping_list = reduce(lambda x, y: [*x, *y], role_permission_mapping_list, []) # 如果是根据角色 if (workspace_user_resource_permission.auth_type == ResourceAuthType.ROLE and workspace_user_resource_permission.permission_list.__contains__( ResourcePermissionRole.ROLE)): return [ f"{role_permission_mapping.permission_id}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}" for role_permission_mapping in role_permission_mapping_list if (permission_constants_dict.get(role_permission_mapping.permission_id).value.parent_group or []).__contains__( WorkspaceGroup(workspace_user_resource_permission.auth_target_type))] + [ f"{workspace_user_resource_permission.auth_target_type}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}"] elif workspace_user_resource_permission.auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP: resource_permission_list = [ [ f"{permission}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}" for permission in get_permission_list_by_resource_group( ResourcePermissionGroup(Resource(workspace_user_resource_permission.auth_target_type), ResourcePermission(resource_permission)))] for resource_permission in workspace_user_resource_permission.permission_list if ResourcePermission.values.__contains__(resource_permission)] # 将二维数组扁平为一维 return reduce(lambda x, y: [*x, *y], resource_permission_list, []) return [] def get_permission_list(user, workspace_user_role_mapping_model, workspace_model, role_model, role_permission_mapping_model): user_id = user.id version = Cache_Version.PERMISSION_LIST.get_version() key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id) # 获取权限列表 is_query_model = workspace_user_role_mapping_model is not None and workspace_model is not None and role_model is not None and role_permission_mapping_model is not None permission_list = cache.get(key, version=version) if permission_list is None: if is_query_model: # 获取工作空间 用户 角色映射数据 workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(user_id=user_id) workspace_user_role_mapping_dict = group_by(workspace_user_role_mapping_list, lambda item: item.workspace_id) role_id_list = list(set([workspace_user_role_mapping.role_id for workspace_user_role_mapping in workspace_user_role_mapping_list])) # 获取角色权限映射数据 role_permission_mapping_list = QuerySet(role_permission_mapping_model).filter( role_id__in=role_id_list) role_model_list = QuerySet(role_model).filter(id__in=role_id_list) role_model_dict = {role_model.id: role_model for role_model in role_model_list} role_permission_mapping_dict = group_by( role_permission_mapping_list, lambda item: item.role_id) workspace_user_permission_list = QuerySet(WorkspaceUserResourcePermission).filter( workspace_id__in=[workspace_user_role.workspace_id for workspace_user_role in workspace_user_role_mapping_list if (role_model_dict.get(workspace_user_role.role_id).type == 'USER' if role_model_dict.get(workspace_user_role.role_id) else False)], user_id=user_id) # 资源权限 workspace_resource_permission_list = get_workspace_resource_permission_list(workspace_user_permission_list, role_permission_mapping_dict, workspace_user_role_mapping_dict) workspace_permission_list = get_workspace_permission_list(role_permission_mapping_dict, workspace_user_role_mapping_list, role_model_dict) # 系统权限 system_permission_list = [role_permission_mapping.permission_id for role_permission_mapping in role_permission_mapping_list] # 合并权限 permission_list = system_permission_list + workspace_permission_list + workspace_resource_permission_list permission_list = list(set(permission_list)) cache.set(key, permission_list, version=version) else: workspace_id_list = ['default'] workspace_user_resource_permission_list = QuerySet(WorkspaceUserResourcePermission).filter( workspace_id__in=workspace_id_list, user_id=user_id) role_permission_mapping_list = get_default_role_permission_mapping_list() role_permission_mapping_dict = group_by(role_permission_mapping_list, lambda item: item.role_id) workspace_user_role_mapping_list = get_default_workspace_user_role_mapping_list([user.role]) workspace_user_role_mapping_dict = group_by(workspace_user_role_mapping_list, lambda item: item.workspace_id) # 资源权限 workspace_resource_permission_list = get_workspace_resource_permission_list( workspace_user_resource_permission_list, role_permission_mapping_dict, workspace_user_role_mapping_dict) # 合并权限 permission_list = workspace_resource_permission_list permission_list = list(set(permission_list)) cache.set(key, permission_list, version=version) return permission_list system_role_list = [RoleConstants.ADMIN.value.name, RoleConstants.WORKSPACE_MANAGE.value.name, RoleConstants.USER.value.name] system_role = RoleConstants.ADMIN.value.name def reset_workspace_role(role_id, workspace_id, role_dict): if system_role_list.__contains__(role_id): if system_role == role_id: return [role_id] else: return [f"{role_id}:/WORKSPACE/{workspace_id}", role_id] else: r = role_dict.get(role_id) if r is None: return '' role_type = role_dict.get(role_id).type if system_role == role_type: return [RoleConstants.EXTENDS_ADMIN.value.name] return [f"EXTENDS_{role_type}:/WORKSPACE/{workspace_id}", f"EXTENDS_{role_type}"] def get_role_list(user, workspace_user_role_mapping_model, workspace_model, role_model, role_permission_mapping_model): """ 获取当前用户的角色列表 """ version = Cache_Version.ROLE_LIST.get_version() key = Cache_Version.ROLE_LIST.get_key(user_id=user.id) workspace_list = cache.get(key, version=version) # 获取权限列表 is_query_model = workspace_user_role_mapping_model is not None and workspace_model is not None and role_model is not None and role_permission_mapping_model is not None if workspace_list is None: if is_query_model: # 获取工作空间 用户 角色映射数据 workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(user_id=user.id) role_list = QuerySet(role_model).filter(id__in=[wurm.role_id for wurm in workspace_user_role_mapping_list]) role_dict = {r.id: r for r in role_list} role_list = list( set(reduce(lambda x, y: [*x, *y], [reset_workspace_role(workspace_user_role_mapping.role_id, workspace_user_role_mapping.workspace_id, role_dict) for workspace_user_role_mapping in workspace_user_role_mapping_list], []))) cache.set(key, workspace_list, version=version) return role_list else: if user.role == RoleConstants.ADMIN.value.__str__(): role_list = [user.role, get_role_permission(RoleConstants.WORKSPACE_MANAGE, 'default')] else: role_list = [user.role, get_role_permission(RoleConstants.USER, 'default')] cache.set(key, role_list, version=version) return role_list return workspace_list def get_auth(user): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") workspace_model = DatabaseModelManage.get_model("workspace_model") role_model = DatabaseModelManage.get_model("role_model") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") permission_list = get_permission_list(user, workspace_user_role_mapping_model, workspace_model, role_model, role_permission_mapping_model) role_list = get_role_list(user, workspace_user_role_mapping_model, workspace_model, role_model, role_permission_mapping_model) return Auth(role_list, permission_list) class UserToken(AuthBaseHandle): def support(self, request, token: str, get_token_details): auth_details = get_token_details() if auth_details is None: return False return 'id' in auth_details and auth_details.get('type') == AuthenticationType.SYSTEM_USER.value def handle(self, request, token: str, get_token_details): version, get_key = Cache_Version.TOKEN.value cache_token = cache.get(get_key(token), version=version) if cache_token is None: raise AppAuthenticationFailed(1002, _('Login expired')) auth_details = get_token_details() timeout = CONFIG.get_session_timeout() cache.touch(token, timeout=timeout, version=version) user = QuerySet(User).get(id=auth_details['id']) if not user.is_active or user.password != cache_token.password: raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) auth = get_auth(user) return user, auth ================================================ FILE: apps/common/cache/__init__.py ================================================ ================================================ FILE: apps/common/cache/mem_cache.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: mem_cache.py @date:2024/3/6 11:20 @desc: """ from django.core.cache.backends.base import DEFAULT_TIMEOUT from django.core.cache.backends.locmem import LocMemCache class MemCache(LocMemCache): def __init__(self, name, params): super().__init__(name, params) def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): key = self.make_and_validate_key(key, version=version) pickled = value with self._lock: self._set(key, pickled, timeout) def get(self, key, default=None, version=None): key = self.make_and_validate_key(key, version=version) with self._lock: if self._has_expired(key): self._delete(key) return default pickled = self._cache[key] self._cache.move_to_end(key, last=False) return pickled def clear_by_application_id(self, application_id): delete_keys = [] for key in self._cache.keys(): value = self._cache.get(key) if (hasattr(value, 'application') and value.application is not None and value.application.id is not None and str( value.application.id) == application_id): delete_keys.append(key) for key in delete_keys: self._delete(key) def clear_timeout_data(self): for key in self._cache.keys(): self.get(key) ================================================ FILE: apps/common/cache_data/__init__.py ================================================ ================================================ FILE: apps/common/cache_data/application_access_token_cache.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: application_access_token_cache.py @date:2024/7/25 11:34 @desc: """ from django.core.cache import cache from django.db.models import QuerySet from application.models import ApplicationAccessToken from common.utils.cache_util import get_cache @get_cache(cache_key=lambda access_token, use_get_data: access_token, use_get_data=lambda access_token, use_get_data: use_get_data, version='APPLICATION_ACCESS_TOKEN_CACHE') def get_application_access_token(access_token, use_get_data): application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first() if application_access_token is None: return None return {'white_active': application_access_token.white_active, 'white_list': application_access_token.white_list, 'application_icon': application_access_token.application.icon, 'application_name': application_access_token.application.name} def del_application_access_token(access_token): cache.delete(access_token, version='APPLICATION_ACCESS_TOKEN_CACHE') ================================================ FILE: apps/common/cache_data/application_api_key_cache.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: application_api_key_cache.py @date:2024/7/25 11:30 @desc: """ from django.core.cache import cache from django.db.models import QuerySet from application.models import ApplicationApiKey from common.constants.cache_version import Cache_Version from common.utils.cache_util import get_cache @get_cache(cache_key=Cache_Version.APPLICATION_API_KEY.get_key_func(), use_get_data=lambda secret_key, use_get_data: use_get_data, version=Cache_Version.APPLICATION_API_KEY.get_version()) def get_application_api_key(secret_key, use_get_data): application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=secret_key[7:]).first() return {'allow_cross_domain': application_api_key.allow_cross_domain, 'cross_domain_list': application_api_key.cross_domain_list} def del_application_api_key(secret_key): cache.delete(Cache_Version.APPLICATION_API_KEY.get_key(secret_key=secret_key, use_get_data=True), version=Cache_Version.APPLICATION_API_KEY.get_version()) ================================================ FILE: apps/common/chunk/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/7/23 17:03 @desc: """ from common.chunk.impl.mark_chunk_handle import MarkChunkHandle handles = [MarkChunkHandle()] def text_to_chunk(text: str, chunk_size: int = 256): chunk_list = [text] for handle in handles: chunk_list = handle.handle(chunk_list, chunk_size) return chunk_list ================================================ FILE: apps/common/chunk/i_chunk_handle.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: i_chunk_handle.py @date:2024/7/23 16:51 @desc: """ from abc import ABC, abstractmethod from typing import List class IChunkHandle(ABC): @abstractmethod def handle(self, chunk_list: List[str], chunk_size: int = 256): pass ================================================ FILE: apps/common/chunk/impl/__init__.py ================================================ ================================================ FILE: apps/common/chunk/impl/mark_chunk_handle.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: mark_chunk_handle.py @date:2024/7/23 16:52 @desc: """ import re from typing import List from common.chunk.i_chunk_handle import IChunkHandle class MarkChunkHandle(IChunkHandle): def handle(self, chunk_list: List[str], chunk_size: int = 256): split_chunk_pattern = r'.{1,%d}[。| |\\.|!|;|;|!|\n]' % chunk_size max_chunk_pattern = r'.{1,%d}' % chunk_size result = [] for chunk in chunk_list: chunk_result = re.findall(split_chunk_pattern, chunk, flags=re.DOTALL) for c_r in chunk_result: if len(c_r.strip()) > 0: result.append(c_r.strip()) other_chunk_list = re.split(split_chunk_pattern, chunk, flags=re.DOTALL) for other_chunk in other_chunk_list: if len(other_chunk) > 0: if len(other_chunk) < chunk_size: if len(other_chunk.strip()) > 0: result.append(other_chunk.strip()) else: max_chunk_list = re.findall(max_chunk_pattern, other_chunk, flags=re.DOTALL) for m_c in max_chunk_list: if len(m_c.strip()) > 0: result.append(m_c.strip()) return result ================================================ FILE: apps/common/config/__init__.py ================================================ ================================================ FILE: apps/common/config/embedding_config.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: embedding_config.py @date:2023/10/23 16:03 @desc: """ import threading import time from common.cache.mem_cache import MemCache _lock = threading.Lock() locks = {} class ModelManage: cache = MemCache('model', {}) up_clear_time = time.time() @staticmethod def _get_lock(_id): lock = locks.get(_id) if lock is None: with _lock: lock = locks.get(_id) if lock is None: lock = threading.Lock() locks[_id] = lock return lock @staticmethod def get_model(_id, get_model): model_instance = ModelManage.cache.get(_id) if model_instance is None: lock = ModelManage._get_lock(_id) with lock: model_instance = ModelManage.cache.get(_id) if model_instance is None: model_instance = get_model(_id) ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8) else: if model_instance.is_cache_model(): ModelManage.cache.touch(_id, timeout=60 * 60 * 8) else: model_instance = get_model(_id) ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8) ModelManage.clear_timeout_cache() return model_instance @staticmethod def clear_timeout_cache(): if time.time() - ModelManage.up_clear_time > 60 * 60: threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start() ModelManage.up_clear_time = time.time() @staticmethod def delete_key(_id): if ModelManage.cache.has_key(_id): ModelManage.cache.delete(_id) class VectorStore: from knowledge.vector.pg_vector import PGVector from knowledge.vector.base_vector import BaseVectorStore instance_map = { 'pg_vector': PGVector, } instance = None @staticmethod def get_embedding_vector() -> BaseVectorStore: from knowledge.vector.pg_vector import PGVector if VectorStore.instance is None: from maxkb.const import CONFIG vector_store_class = VectorStore.instance_map.get(CONFIG.get("VECTOR_STORE_NAME"), PGVector) VectorStore.instance = vector_store_class() return VectorStore.instance ================================================ FILE: apps/common/config/tokenizer_manage_config.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: tokenizer_manage_config.py @date:2024/4/28 10:17 @desc: """ import os class MKTokenizer: def __init__(self, tokenizer): self.tokenizer = tokenizer def encode(self, text): return self.tokenizer.encode(text).ids class TokenizerManage: tokenizer = None @staticmethod def get_tokenizer(): from tokenizers import Tokenizer # 创建Tokenizer model_path = os.path.join("/opt/maxkb-app", "model", "tokenizer", "models--bert-base-cased") with open(f"{model_path}/refs/main", encoding="utf-8") as f: snapshot = f.read() TokenizerManage.tokenizer = Tokenizer.from_file(f"{model_path}/snapshots/{snapshot}/tokenizer.json") return MKTokenizer(TokenizerManage.tokenizer) ================================================ FILE: apps/common/constants/__init__.py ================================================ ================================================ FILE: apps/common/constants/authentication_type.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎虎 @file: authentication_type.py @date:2023/11/14 20:03 @desc: """ from enum import Enum class AuthenticationType(Enum): # 系统用户 SYSTEM_USER = "SYSTEM_USER" # 对话用户 CHAT_USER = "CHAT_USER" # 对话匿名用户 CHAT_ANONYMOUS_USER = "CHAT_ANONYMOUS_USER" # APIKEY API_KEY = "API_KEY" ================================================ FILE: apps/common/constants/cache_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: cache_version.py @date:2025/4/14 19:09 @desc: """ from enum import Enum class Cache_Version(Enum): # 令牌 TOKEN = "TOKEN", lambda token: token # 工作空间列表 WORKSPACE_LIST = "WORKSPACE:LIST", lambda user_id: user_id # 用户数据 USER = "USER", lambda user_id: user_id # 当前用户所有的角色 ROLE_LIST = "ROLE:LIST", lambda user_id: user_id # 当前用户所有权限 PERMISSION_LIST = "PERMISSION:LIST", lambda user_id: user_id # 验证码 CAPTCHA = "CAPTCHA", lambda captcha: captcha # 系统 SYSTEM = "SYSTEM", lambda key: key # 应用对接三方应用的缓存 APPLICATION_THIRD_PARTY = "APPLICATION:THIRD_PARTY", lambda key: key KNOWLEDGE_WORKFLOW_INTERRUPTED = "KNOWLEDGE_WORKFLOW_INTERRUPTED", lambda action_id: action_id # 对话 CHAT = "CHAT", lambda key: key CHAT_INFO = "CHAT_INFO", lambda key: key CHAT_VARIABLE = "CHAT_VARIABLE", lambda key: key # 应用API KEY APPLICATION_API_KEY = "APPLICATION_API_KEY", lambda secret_key, use_get_data: secret_key CHAT_USER_TOKEN = "CHAT_USER_TOKEN", lambda token: token TOOL_WORKFLOW_EXECUTE = "TOOL_WORKFLOW_EXECUTE", lambda key: key def get_version(self): return self.value[0] def get_key_func(self): return self.value[1] def get_key(self, **kwargs): return self.value[1](**kwargs) ================================================ FILE: apps/common/constants/exception_code_constants.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: exception_code_constants.py @date:2023/9/4 14:09 @desc: 异常常量类 """ from enum import Enum from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ class ExceptionCodeConstantsValue: def __init__(self, code, message): self.code = code self.message = message def get_message(self): return self.message def get_code(self): return self.code def to_app_api_exception(self): return AppApiException(code=self.code, message=self.message) class ExceptionCodeConstants(Enum): INCORRECT_USERNAME_AND_PASSWORD = ExceptionCodeConstantsValue(1000, _('The username or password is incorrect')) NOT_AUTHENTICATION = ExceptionCodeConstantsValue(1001, _('Please log in first and bring the user Token')) EMAIL_SEND_ERROR = ExceptionCodeConstantsValue(1002, _('Email sending failed')) EMAIL_FORMAT_ERROR = ExceptionCodeConstantsValue(1003, _('Email format error')) EMAIL_IS_EXIST = ExceptionCodeConstantsValue(1004, _('The email has been registered, please log in directly')) EMAIL_IS_NOT_EXIST = ExceptionCodeConstantsValue(1005, _('The email is not registered, please register first')) CODE_ERROR = ExceptionCodeConstantsValue(1005, _('The verification code is incorrect or the verification code has expired')) USERNAME_IS_EXIST = ExceptionCodeConstantsValue(1006, _('The username has been registered, please log in directly')) USERNAME_ERROR = ExceptionCodeConstantsValue(1006, _('The username cannot be empty and must be between 6 and 20 characters long.')) PASSWORD_NOT_EQ_RE_PASSWORD = ExceptionCodeConstantsValue(1007, _('Password and confirmation password are inconsistent')) NICKNAME_IS_EXIST = ExceptionCodeConstantsValue(1008, _('The nickname is already registered')) SEND_EMAIL_ERROR = ExceptionCodeConstantsValue(1009, _("Email sending failed")) ================================================ FILE: apps/common/constants/permission_constants.py ================================================ """ @project: qabot @Author:虎虎 @file: permission_constants.py @date:2023/9/13 18:23 @desc: 权限,角色 常量 """ from enum import Enum from functools import reduce from typing import List from django.db import models from django.utils.translation import gettext_lazy as _ from maxkb import settings class Group(Enum): """ 权限组 一个组一般对应前端一个菜单 """ USER = "USER_MANAGEMENT" # 应用 APPLICATION = "APPLICATION" # 应用概览 APPLICATION_OVERVIEW = "APPLICATION_OVERVIEW" # 应用接入 APPLICATION_ACCESS = "APPLICATION_ACCESS" # 应用 对话用户 APPLICATION_CHAT_USER = "APPLICATION_CHAT_USER" # 知识库 对话用户 KNOWLEDGE_CHAT_USER = "KNOWLEDGE_CHAT_USER" # 应用对话日志 APPLICATION_CHAT_LOG = "APPLICATION_CHAT_LOG" KNOWLEDGE = "KNOWLEDGE" SYSTEM_KNOWLEDGE = "SYSTEM_KNOWLEDGE" SYSTEM_RES_KNOWLEDGE = "SYSTEM_RESOURCE_KNOWLEDGE" KNOWLEDGE_HIT_TEST = "KNOWLEDGE_HIT_TEST" KNOWLEDGE_DOCUMENT = "KNOWLEDGE_DOCUMENT" KNOWLEDGE_WORKFLOW = "KNOWLEDGE_WORKFLOW" KNOWLEDGE_TAG = "KNOWLEDGE_TAG" SYSTEM_KNOWLEDGE_DOCUMENT = "SYSTEM_KNOWLEDGE_DOCUMENT" SYSTEM_KNOWLEDGE_WORKFLOW = "SYSTEM_KNOWLEDGE_WORKFLOW" SYSTEM_RES_KNOWLEDGE_DOCUMENT = "SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT" SYSTEM_RES_KNOWLEDGE_WORKFLOW = "SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW" SYSTEM_RES_KNOWLEDGE_TAG = "SYSTEM_RES_KNOWLEDGE_TAG" SYSTEM_KNOWLEDGE_TAG = "SYSTEM_KNOWLEDGE_TAG" KNOWLEDGE_PROBLEM = "KNOWLEDGE_PROBLEM" SYSTEM_KNOWLEDGE_PROBLEM = "SYSTEM_KNOWLEDGE_PROBLEM" SYSTEM_RES_KNOWLEDGE_PROBLEM = "SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM" SYSTEM_KNOWLEDGE_HIT_TEST = "SYSTEM_KNOWLEDGE_HIT_TEST" SYSTEM_RES_KNOWLEDGE_HIT_TEST = "SYSTEM_RESOURCE_KNOWLEDGE_HIT_TEST" SYSTEM_KNOWLEDGE_CHAT_USER = "SYSTEM_KNOWLEDGE_CHAT_USER" SYSTEM_RES_KNOWLEDGE_CHAT_USER = "SYSTEM_RESOURCE_KNOWLEDGE_CHAT_USER" MODEL = "MODEL" SYSTEM_MODEL = "SYSTEM_MODEL" SYSTEM_RES_MODEL = "SYSTEM_RESOURCE_MODEL" SYSTEM_RES_APPLICATION = "SYSTEM_RESOURCE_APPLICATION" SYSTEM_RES_APPLICATION_OVERVIEW = "SYSTEM_RESOURCE_APPLICATION_OVERVIEW" SYSTEM_RES_APPLICATION_ACCESS = "SYSTEM_RESOURCE_APPLICATION_ACCESS" SYSTEM_RES_APPLICATION_CHAT_USER = "SYSTEM_RESOURCE_APPLICATION_CHAT_USER" SYSTEM_RES_APPLICATION_CHAT_LOG = "SYSTEM_RESOURCE_APPLICATION_CHAT_LOG" TOOL = "TOOL" SYSTEM_TOOL = "SYSTEM_TOOL" SYSTEM_RES_TOOL = "SYSTEM_RESOURCE_TOOL" TRIGGER = "TRIGGER" APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION = "APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION" KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION = "KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION" TOOL_WORKSPACE_USER_RESOURCE_PERMISSION = "TOOL_WORKSPACE_USER_RESOURCE_PERMISSION" MODEL_WORKSPACE_USER_RESOURCE_PERMISSION = "MODEL_WORKSPACE_USER_RESOURCE_PERMISSION" EMAIL_SETTING = "EMAIL_SETTING" ROLE = "ROLE" WORKSPACE_ROLE = "WORKSPACE_ROLE" WORKSPACE = "WORKSPACE" WORKSPACE_WORKSPACE = "WORKSPACE_WORKSPACE" DISPLAY_SETTINGS = "DISPLAY_SETTINGS" LOGIN_AUTH = "LOGIN_AUTH" SYSTEM_API_KEY = "SYSTEM_API_KEY" APPEARANCE_SETTINGS = "APPEARANCE_SETTINGS" CHAT_USER = "CHAT_USER" WORKSPACE_CHAT_USER = "WORKSPACE_CHAT_USER" USER_GROUP = "USER_GROUP" WORKSPACE_USER_GROUP = "WORKSPACE_USER_GROUP" CHAT_USER_AUTH = "CHAT_USER_AUTH" OTHER = "OTHER" OVERVIEW = "OVERVIEW" OPERATION_LOG = "OPERATION_LOG" APPLICATION_FOLDER = "APPLICATION_FOLDER" KNOWLEDGE_FOLDER = "KNOWLEDGE_FOLDER" TOOL_FOLDER = "TOOL_FOLDER" class SystemGroup(Enum): """ 一级菜单 """ USER_MANAGEMENT = "USER_MANAGEMENT" ROLE = "ROLE" WORKSPACE = "WORKSPACE" # RESOURCE = "RESOURCE" RESOURCE_APPLICATION = "RESOURCE_APPLICATION" RESOURCE_KNOWLEDGE = "RESOURCE_KNOWLEDGE" RESOURCE_TOOL = "RESOURCE_TOOL" RESOURCE_MODEL = "RESOURCE_MODEL" RESOURCE_PERMISSION = "RESOURCE_PERMISSION" SHARED_KNOWLEDGE = "SHARED_KNOWLEDGE" SHARED_MODEL = "SHARED_MODEL" SHARED_TOOL = "SHARED_TOOL" CHAT_USER = "CHAT_USER" SYSTEM_SETTING = "SYSTEM_SETTING" OPERATION_LOG = "OPERATION_LOG" OTHER = "OTHER" class WorkspaceGroup(Enum): SYSTEM_MANAGEMENT = "SYSTEM_MANAGEMENT" APPLICATION = "APPLICATION" KNOWLEDGE = "KNOWLEDGE" MODEL = "MODEL" TOOL = "TOOL" TRIGGER = "TRIGGER" RESOURCE_PERMISSION = "RESOURCE_PERMISSION" OTHER = "OTHER" class UserGroup(Enum): APPLICATION = "APPLICATION" KNOWLEDGE = "KNOWLEDGE" MODEL = "MODEL" TOOL = "TOOL" OTHER = "OTHER" class Operate(Enum): """ 一个权限组的操作权限 """ SELF = "" READ = 'READ' EDIT = "READ+EDIT" CREATE = "READ+CREATE" DELETE = "READ+DELETE" """ 使用权限 """ USE = "USE" IMPORT = "READ+IMPORT" EXPORT = "READ+EXPORT" # 导入导出 SYNC = "READ+SYNC" # 同步 GENERATE = "READ+GENERATE" # 生成 ADD_MEMBER = "READ+ADD_MEMBER" # 添加成员 REMOVE_MEMBER = "READ+REMOVE_MEMBER" # 添加成员 VECTOR = "READ+VECTOR" # 向量化 MIGRATE = "READ+MIGRATE" # 迁移 RELATE = "READ+RELATE" # 关联 USER_GROUP = "READ+USER_GROUP" # 用户组 ANNOTATION = "READ+ANNOTATION" # 标注 CLEAR_POLICY = "READ+CLEAR_POLICY" EMBED = "READ+EMBED" # 嵌入 ACCESS = "READ+ACCESS" # 访问限制 DISPLAY = "READ+DISPLAY" # 显示设置 API_KEY = "READ+API_KEY" # API_KEY PUBLIC_ACCESS = "READ+PUBLIC_ACCESS" # 公共访问链接 Q_WEIXIN = "READ+Q_WEIXIN" # 企业微信 FEISHU = "READ+FEISHU" # 飞书 DD = "READ+DD" # 钉钉 WEIXIN_PUBLIC_ACCOUNT = "READ+WEIXIN_PUBLIC_ACCOUNT" # 微信公众号 SLACK = "READ+SLACK" # SLACK ADD_KNOWLEDGE = "READ+ADD_KNOWLEDGE" # 添加到知识库 TO_CHAT = "READ+TO_CHAT" # 去对话 SETTING = "READ+SETTING" # 管理 DOWNLOAD = "READ+DOWNLOAD" # 下载 AUTH = "READ+AUTH" # 资源授权 TAG = "READ+TAG" # 标签设置 REPLACE = "READ+REPLACE" # 标签设置 UPDATE = "READ+UPDATE" # 更新license RELATE_VIEW = "READ+RELATE_VIEW" RECORD = "READ+RECORD" TRIGGER_READ = "READ+TRIGGER_READ" TRIGGER_EDIT = "READ+TRIGGER_EDIT" TRIGGER_CREATE = "READ+TRIGGER_CREATE" TRIGGER_DELETE = "READ+TRIGGER_DELETE" class RoleGroup(Enum): # 系统用户 SYSTEM_USER = "SYSTEM_USER" # 对话用户 CHAT_USER = "CHAT_USER" class ResourcePermissionRole(models.TextChoices): """ 资源权限根据角色 """ ROLE = "ROLE" def __eq__(self, other): return str(self) == str(other) class ResourcePermission(models.TextChoices): """ 资源权限组 """ # 查看 VIEW = "VIEW" # 管理 MANAGE = "MANAGE" def __eq__(self, other): return str(self) == str(other) class Resource(models.TextChoices): KNOWLEDGE = Group.KNOWLEDGE.value KNOWLEDGE_FOLDER = Group.KNOWLEDGE_FOLDER.value APPLICATION = Group.APPLICATION.value APPLICATION_FOLDER = Group.APPLICATION_FOLDER.value TOOL = Group.TOOL.value TOOL_FOLDER = Group.TOOL_FOLDER.value MODEL = Group.MODEL.value def __eq__(self, other): return str(self) == str(other) class ResourcePermissionGroup: def __init__(self, resource: Resource, permission: ResourcePermission): self.permission = permission self.resource = resource def __eq__(self, other): return str(self.permission) == str(other.permission) and str(self.resource) == str(other.resource) class ResourcePermissionConst: KNOWLEDGE_MANGE = ResourcePermissionGroup(Resource.KNOWLEDGE, ResourcePermission.MANAGE) KNOWLEDGE_FOLDER_MANGE = ResourcePermissionGroup(Resource.KNOWLEDGE_FOLDER, ResourcePermission.MANAGE) KNOWLEDGE_FOLDER_VIEW = ResourcePermissionGroup(Resource.KNOWLEDGE_FOLDER, ResourcePermission.VIEW) KNOWLEDGE_VIEW = ResourcePermissionGroup(Resource.KNOWLEDGE, ResourcePermission.VIEW) APPLICATION_MANGE = ResourcePermissionGroup(Resource.APPLICATION, ResourcePermission.MANAGE) APPLICATION_FOLDER_MANGE = ResourcePermissionGroup(Resource.APPLICATION_FOLDER, ResourcePermission.MANAGE) APPLICATION_FOLDER_VIEW = ResourcePermissionGroup(Resource.APPLICATION_FOLDER, ResourcePermission.VIEW) APPLICATION_VIEW = ResourcePermissionGroup(Resource.APPLICATION, ResourcePermission.VIEW) TOOL_MANGE = ResourcePermissionGroup(Resource.TOOL, ResourcePermission.MANAGE) TOOL_FOLDER_MANGE = ResourcePermissionGroup(Resource.TOOL_FOLDER, ResourcePermission.MANAGE) TOOL_FOLDER_VIEW = ResourcePermissionGroup(Resource.TOOL_FOLDER, ResourcePermission.VIEW) TOOL_VIEW = ResourcePermissionGroup(Resource.TOOL, ResourcePermission.VIEW) MODEL_MANGE = ResourcePermissionGroup(Resource.MODEL, ResourcePermission.MANAGE) MODEL_VIEW = ResourcePermissionGroup(Resource.MODEL, ResourcePermission.VIEW) class ResourceAuthType(models.TextChoices): """ 资源授权类型 """ "当授权类型是Role时候" ROLE = "ROLE" """资源权限组""" RESOURCE_PERMISSION_GROUP = "RESOURCE_PERMISSION_GROUP" class Role: def __init__(self, name: str, decs: str, group: RoleGroup, resource_path=None): self.name = name self.decs = decs self.group = group self.resource_path = resource_path def __str__(self): return self.name + ( (":" + self.resource_path) if self.resource_path is not None else '') def __eq__(self, other): return str(self) == str(other) def get_workspace_role(self): return lambda r, kwargs: Role(self.name, self.decs, self.group, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}") class RoleConstants(Enum): ADMIN = Role("ADMIN", '超级管理员', RoleGroup.SYSTEM_USER) WORKSPACE_MANAGE = Role("WORKSPACE_MANAGE", '工作空间管理员', RoleGroup.SYSTEM_USER) USER = Role("USER", '普通用户', RoleGroup.SYSTEM_USER) CHAT_ANONYMOUS_USER = Role("CHAT_ANONYMOUS_USER", "对话匿名用户", RoleGroup.CHAT_USER) CHAT_USER = Role("CHAT_USER", "对话用户", RoleGroup.CHAT_USER) EXTENDS_ADMIN = Role("EXTENDS_ADMIN", '继承超级管理员', RoleGroup.SYSTEM_USER) EXTENDS_WORKSPACE_MANAGE = Role("EXTENDS_WORKSPACE_MANAGE", "继承工作空间管理员", RoleGroup.CHAT_USER) EXTENDS_USER = Role("EXTENDS_USER", "继承普通用户", RoleGroup.CHAT_USER) def get_workspace_role(self): return lambda r, kwargs: Role(name=self.value.name, decs=self.value.decs, group=self.value.group, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}") Permission_Label = { SystemGroup.SYSTEM_SETTING.value: _("System Setting"), SystemGroup.USER_MANAGEMENT.value: _("User Management"), SystemGroup.ROLE.value: _("Role"), SystemGroup.WORKSPACE.value: _("Workspace"), SystemGroup.RESOURCE_APPLICATION.value: _("Resource Application"), SystemGroup.RESOURCE_KNOWLEDGE.value: _("Resource Knowledge"), SystemGroup.RESOURCE_TOOL.value: _("Resource Tool"), SystemGroup.RESOURCE_MODEL.value: _("Resource Model"), SystemGroup.RESOURCE_PERMISSION.value: _("Resource Permission"), SystemGroup.SHARED_KNOWLEDGE.value: _("Shared Knowledge"), SystemGroup.SHARED_MODEL.value: _("Shared Model"), SystemGroup.SHARED_TOOL.value: _("Shared Tool"), SystemGroup.OPERATION_LOG.value: _("Operation Log"), SystemGroup.OTHER.value: _("Other"), WorkspaceGroup.SYSTEM_MANAGEMENT.value: _("System Management"), WorkspaceGroup.APPLICATION.value: _("Application"), WorkspaceGroup.KNOWLEDGE.value: _("Knowledge"), WorkspaceGroup.MODEL.value: _("Model"), WorkspaceGroup.TOOL.value: _("Tool"), WorkspaceGroup.TRIGGER.value: _("Trigger"), WorkspaceGroup.OTHER.value: _("Other"), Operate.READ.value: _("Read"), Operate.EDIT.value: _("Edit"), Operate.CREATE.value: _("Create"), Operate.DELETE.value: _("Delete"), Group.EMAIL_SETTING.value: _("Email Setting"), Group.APPLICATION.value: _("Application"), Group.KNOWLEDGE.value: _("Knowledge"), Group.KNOWLEDGE_DOCUMENT.value: _("Document"), Group.KNOWLEDGE_WORKFLOW.value: _("Workflow"), Group.KNOWLEDGE_TAG.value: _("Tag"), Group.KNOWLEDGE_PROBLEM.value: _("Problem"), Group.KNOWLEDGE_HIT_TEST.value: _("Hit-Test"), Operate.IMPORT.value: _("Import"), Operate.EXPORT.value: _("Export"), Operate.SYNC.value: _("Sync"), Operate.GENERATE.value: _("Generate"), Operate.ADD_MEMBER.value: _("Add Member"), Operate.REMOVE_MEMBER.value: _("Remove Member"), Operate.VECTOR.value: _("Vector"), Operate.MIGRATE.value: _("Migrate"), Operate.RELATE.value: _("Relate"), Operate.ANNOTATION.value: _("Annotation"), Operate.CLEAR_POLICY.value: _("Clear Policy"), Operate.DOWNLOAD.value: _('Download Original Document'), Operate.EMBED.value: _('Embed third party'), Operate.ACCESS.value: _('Access restrictions'), Operate.DISPLAY.value: _('Display Settings'), Operate.API_KEY.value: _('API KEY'), Operate.PUBLIC_ACCESS.value: _('Public access link'), Operate.Q_WEIXIN.value: _('Enterprise WeiXin'), Operate.FEISHU.value: _('Feishu'), Operate.DD.value: _('Dingding'), Operate.WEIXIN_PUBLIC_ACCOUNT.value: _('Weixin Public Account'), Operate.ADD_KNOWLEDGE.value: _('Add to Knowledge Base'), Operate.AUTH.value: _('resource authorization'), Operate.TAG.value: _('Tag Setting'), Operate.REPLACE.value: _('Replace Original Document'), Operate.RELATE_VIEW.value: _('View related resources'), Operate.TRIGGER_READ.value: _('Read Trigger'), Operate.TRIGGER_CREATE.value: _('Create Trigger'), Operate.TRIGGER_EDIT.value: _('Edit Trigger'), Operate.TRIGGER_DELETE.value: _('Delete Trigger'), Operate.RECORD.value: _('Read execute record'), Group.APPLICATION_OVERVIEW.value: _('Overview'), Group.APPLICATION_ACCESS.value: _('Application Access'), Group.APPLICATION_CHAT_USER.value: _('Dialogue users'), Group.APPLICATION_CHAT_LOG.value: _('Conversation log'), Group.KNOWLEDGE_CHAT_USER.value: _('Dialogue users'), Group.LOGIN_AUTH.value: _("Login Auth"), Group.DISPLAY_SETTINGS.value: _("Display Settings"), Group.SYSTEM_API_KEY.value: _("System API Key"), Group.APPEARANCE_SETTINGS.value: _("Appearance Settings"), Group.CHAT_USER.value: _("Chat User"), Group.USER_GROUP.value: _("User Group"), Group.CHAT_USER_AUTH.value: _("Chat User Auth"), Group.OVERVIEW.value: _("Overview"), Group.SYSTEM_TOOL.value: _("Tool"), Group.SYSTEM_MODEL.value: _("Model"), Group.SYSTEM_KNOWLEDGE.value: _("Knowledge"), Group.SYSTEM_KNOWLEDGE_DOCUMENT.value: _("Document"), Group.SYSTEM_KNOWLEDGE_WORKFLOW.value: _("Workflow"), Group.SYSTEM_KNOWLEDGE_TAG.value: _("Tag"), Group.SYSTEM_KNOWLEDGE_PROBLEM.value: _("Problem"), Group.SYSTEM_KNOWLEDGE_HIT_TEST.value: _("Hit-Test"), Group.SYSTEM_KNOWLEDGE_CHAT_USER.value: _("Dialogue users"), Group.SYSTEM_RES_TOOL.value: _("Tool"), Group.SYSTEM_RES_MODEL.value: _("Model"), Group.SYSTEM_RES_KNOWLEDGE.value: _("Knowledge"), Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT.value: _("Document"), Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW.value: _("Workflow"), Group.SYSTEM_RES_KNOWLEDGE_TAG.value: _("Tag"), Group.SYSTEM_RES_KNOWLEDGE_PROBLEM.value: _("Problem"), Group.SYSTEM_RES_KNOWLEDGE_HIT_TEST.value: _("Hit-Test"), Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER.value: _("Dialogue users"), Group.WORKSPACE_USER_GROUP.value: _("User Group"), Group.WORKSPACE_CHAT_USER.value: _("Chat User"), Group.WORKSPACE_WORKSPACE.value: _("Workspace"), Group.WORKSPACE_ROLE.value: _("Role"), Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION.value: _("Application"), Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION.value: _("Knowledge"), Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION.value: _("Model"), Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION.value: _("Tool"), Group.SYSTEM_RES_APPLICATION.value: _("Application"), Group.SYSTEM_RES_APPLICATION_OVERVIEW.value: _("Overview"), Group.SYSTEM_RES_APPLICATION_ACCESS.value: _("Application Access"), Group.SYSTEM_RES_APPLICATION_CHAT_USER.value: _("Dialogue users"), Group.SYSTEM_RES_APPLICATION_CHAT_LOG.value: _("Conversation log"), Group.APPLICATION_FOLDER.value: _("Folder"), Group.KNOWLEDGE_FOLDER.value: _("Folder"), Group.TOOL_FOLDER.value: _("Folder"), # SystemGroup.RESOURCE.value: _("Resource"), } class Permission: """ 权限信息 """ def __init__(self, group: Group, operate: Operate, resource_path=None, role_list=None, resource_permission_group_list=None, parent_group=None, label=None, is_ee=True): if role_list is None: role_list = [] if resource_permission_group_list is None: resource_permission_group_list = [] self.group = group self.operate = operate self.resource_path = resource_path # 用于获取角色与权限的关系,只适用于没有权限管理的 self.role_list = role_list # 用于资源权限权限分组 self.resource_permission_group_list = resource_permission_group_list self.parent_group = parent_group # 新增字段:父级组 self.label = label self.is_ee = is_ee # 是否是企业版权限 @staticmethod def new_instance(permission_str: str): permission_split = permission_str.split(":") group = Group[permission_split[0]] operate = Operate[permission_split[1]] if len(permission_split) > 2: dynamic_tag = ":".join(permission_split[2:]) return Permission(group, operate, dynamic_tag) return Permission(group, operate) def __str__(self): return self.group.value + ( (":" + self.operate.value) if self.operate.value else '') + ( (":" + self.resource_path) if self.resource_path is not None else '') def __eq__(self, other): return str(self) == str(other) class PermissionConstants(Enum): """ 权限枚举 """ KNOWLEDGE = Permission( group=Group.KNOWLEDGE, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER] ) APPLICATION = Permission( group=Group.APPLICATION, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER], ) MODEL = Permission( group=Group.MODEL, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER], ) TOOL = Permission( group=Group.TOOL, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER], ) USER_READ = Permission( group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.USER_MANAGEMENT] ) USER_CREATE = Permission( group=Group.USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.USER_MANAGEMENT] ) USER_EDIT = Permission( group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.USER_MANAGEMENT] ) USER_DELETE = Permission( group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.USER_MANAGEMENT] ) MODEL_READ = Permission( group=Group.MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_VIEW] ) MODEL_CREATE = Permission( group=Group.MODEL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE] ) MODEL_EDIT = Permission( group=Group.MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE] ) MODEL_DELETE = Permission( group=Group.MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE] ) MODEL_RESOURCE_AUTHORIZATION = Permission( group=Group.MODEL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE] ) MODEL_RELATE_RESOURCE_VIEW = Permission( group=Group.MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL], resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE] ) # trigger TRIGGER_READ = Permission( group=Group.TRIGGER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.TRIGGER], ) TRIGGER_CREATE = Permission( group=Group.TRIGGER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.TRIGGER], ) TRIGGER_EDIT = Permission( group=Group.TRIGGER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.TRIGGER], ) TRIGGER_DELETE = Permission( group=Group.TRIGGER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.TRIGGER], ) TRIGGER_RECORD = Permission( group=Group.TRIGGER, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.TRIGGER], ) TOOL_READ = Permission( group=Group.TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] ) TOOL_CREATE = Permission( group=Group.TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_EDIT = Permission( group=Group.TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_DELETE = Permission( group=Group.TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_IMPORT = Permission( group=Group.TOOL, operate=Operate.IMPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_EXPORT = Permission( group=Group.TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_RESOURCE_AUTHORIZATION = Permission( group=Group.TOOL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_RELATE_RESOURCE_VIEW = Permission( group=Group.TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_EXECUTE_RECORD = Permission( group=Group.TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) # source point trigger TOOL_TRIGGER_READ = Permission( group=Group.TOOL, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_TRIGGER_CREATE = Permission( group=Group.TOOL, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] ) TOOL_TRIGGER_EDIT = Permission( group=Group.TOOL, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] ) TOOL_TRIGGER_DELETE = Permission( group=Group.TOOL, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] ) TOOL_FOLDER_READ = Permission( group=Group.TOOL_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] ) TOOL_FOLDER_CREATE = Permission( group=Group.TOOL_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_FOLDER_EDIT = Permission( group=Group.TOOL_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_FOLDER_DELETE = Permission( group=Group.TOOL_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) TOOL_FOLDER_AUTH = Permission( group=Group.TOOL_FOLDER, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) KNOWLEDGE_READ = Permission( group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_CREATE = Permission( group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_EDIT = Permission( group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DELETE = Permission( group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_SYNC = Permission( group=Group.KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_EXPORT = Permission( group=Group.KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_VECTOR = Permission( group=Group.KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_GENERATE = Permission( group=Group.KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_RESOURCE_AUTHORIZATION = Permission( group=Group.KNOWLEDGE, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission( group=Group.KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE] ) KNOWLEDGE_FOLDER_READ = Permission( group=Group.KNOWLEDGE_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[UserGroup.KNOWLEDGE] ) KNOWLEDGE_FOLDER_CREATE = Permission( group=Group.KNOWLEDGE_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_FOLDER_EDIT = Permission( group=Group.KNOWLEDGE_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_FOLDER_DELETE = Permission( group=Group.KNOWLEDGE_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_FOLDER_AUTH = Permission( group=Group.KNOWLEDGE_FOLDER, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_WORKFLOW_READ = Permission( group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_WORKFLOW_EDIT = Permission( group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_WORKFLOW_EXPORT = Permission( group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_READ = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_CREATE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_EDIT = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_DELETE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_SYNC = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_EXPORT = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_GENERATE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_VECTOR = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_MIGRATE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_TAG = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.TAG, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_DOCUMENT_REPLACE = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_HIT_TEST = Permission( group=Group.KNOWLEDGE_HIT_TEST, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_PROBLEM_READ = Permission( group=Group.KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_PROBLEM_CREATE = Permission( group=Group.KNOWLEDGE_PROBLEM, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_PROBLEM_EDIT = Permission( group=Group.KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_PROBLEM_DELETE = Permission( group=Group.KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_PROBLEM_RELATE = Permission( group=Group.KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_TAG_READ = Permission( group=Group.KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_TAG_CREATE = Permission( group=Group.KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_TAG_EDIT = Permission( group=Group.KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) KNOWLEDGE_TAG_DELETE = Permission( group=Group.KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission( group=Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission( group=Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission( group=Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission( group=Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission( group=Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission( group=Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission( group=Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission( group=Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE], parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION] ) EMAIL_SETTING_READ = Permission( group=Group.EMAIL_SETTING, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) EMAIL_SETTING_EDIT = Permission( group=Group.EMAIL_SETTING, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) ROLE_READ = Permission( group=Group.ROLE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.ROLE] ) ROLE_CREATE = Permission( group=Group.ROLE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.ROLE] ) ROLE_EDIT = Permission( group=Group.ROLE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.ROLE] ) ROLE_DELETE = Permission( group=Group.ROLE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.ROLE] ) ROLE_ADD_MEMBER = Permission( group=Group.ROLE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.ROLE] ) ROLE_REMOVE_MEMBER = Permission( group=Group.ROLE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.ROLE] ) WORKSPACE_ROLE_READ = Permission( group=Group.WORKSPACE_ROLE, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_ROLE_ADD_MEMBER = Permission( group=Group.WORKSPACE_ROLE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_ROLE_REMOVE_MEMBER = Permission( group=Group.WORKSPACE_ROLE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_READ = Permission( group=Group.WORKSPACE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_CREATE = Permission( group=Group.WORKSPACE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_EDIT = Permission( group=Group.WORKSPACE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_DELETE = Permission( group=Group.WORKSPACE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_ADD_MEMBER = Permission( group=Group.WORKSPACE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_REMOVE_MEMBER = Permission( group=Group.WORKSPACE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == "EE" ) WORKSPACE_WORKSPACE_READ = Permission( group=Group.WORKSPACE_WORKSPACE, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == "EE" ) WORKSPACE_WORKSPACE_ADD_MEMBER = Permission( group=Group.WORKSPACE_WORKSPACE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == "EE" ) WORKSPACE_WORKSPACE_REMOVE_MEMBER = Permission( group=Group.WORKSPACE_WORKSPACE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == "EE" ) LOGIN_AUTH_READ = Permission( group=Group.LOGIN_AUTH, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) LOGIN_AUTH_EDIT = Permission( group=Group.LOGIN_AUTH, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) APPLICATION_READ = Permission(group=Group.APPLICATION, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW], ) APPLICATION_CREATE = Permission(group=Group.APPLICATION, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_EDIT = Permission(group=Group.APPLICATION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_DELETE = Permission(group=Group.APPLICATION, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_IMPORT = Permission(group=Group.APPLICATION, operate=Operate.IMPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_EXPORT = Permission(group=Group.APPLICATION, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], ) APPLICATION_RESOURCE_AUTHORIZATION = Permission(group=Group.APPLICATION, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_TRIGGER_READ = Permission( group=Group.APPLICATION, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_TRIGGER_CREATE = Permission( group=Group.APPLICATION, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_TRIGGER_EDIT = Permission( group=Group.APPLICATION, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_TRIGGER_DELETE = Permission( group=Group.APPLICATION, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_FOLDER_READ = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW] ) APPLICATION_FOLDER_CREATE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_FOLDER_EDIT = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_FOLDER_DELETE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_FOLDER_AUTH = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] ) APPLICATION_OVERVIEW_READ = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW], ) APPLICATION_OVERVIEW_EMBED = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.EMBED, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_OVERVIEW_ACCESS = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.ACCESS, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_OVERVIEW_DISPLAY = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.DISPLAY, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_OVERVIEW_API_KEY = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.API_KEY, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_OVERVIEW_PUBLIC = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.PUBLIC_ACCESS, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) # 应用接入 APPLICATION_ACCESS_READ = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW], ) APPLICATION_ACCESS_EDIT = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]) APPLICATION_CHAT_USER_READ = Permission(group=Group.APPLICATION_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW], ) APPLICATION_CHAT_USER_EDIT = Permission(group=Group.APPLICATION_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) KNOWLEDGE_CHAT_USER_READ = Permission(group=Group.KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], ) KNOWLEDGE_CHAT_USER_EDIT = Permission(group=Group.KNOWLEDGE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], ) APPLICATION_CHAT_LOG_READ = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW], ) APPLICATION_CHAT_LOG_ANNOTATION = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.ANNOTATION, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_CHAT_LOG_EXPORT = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_CHAT_LOG_CLEAR_POLICY = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.CLEAR_POLICY, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) APPLICATION_CHAT_LOG_ADD_KNOWLEDGE = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.ADD_KNOWLEDGE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) ABOUT_READ = Permission(group=Group.OTHER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER], label=_('About') ) ABOUT_UPDATE = Permission(group=Group.OTHER, operate=Operate.UPDATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.OTHER], label=_('Update License') ) SWITCH_LANGUAGE = Permission(group=Group.OTHER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER], label=_('Switch Language') ) CHANGE_PASSWORD = Permission(group=Group.OTHER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER], label=_('Change Password') ) SYSTEM_API_KEY_EDIT = Permission(group=Group.OTHER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER], label=_('System API Key') ) APPEARANCE_SETTINGS_READ = Permission(group=Group.APPEARANCE_SETTINGS, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) APPEARANCE_SETTINGS_EDIT = Permission(group=Group.APPEARANCE_SETTINGS, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) CHAT_USER_READ = Permission(group=Group.CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER], ) CHAT_USER_CREATE = Permission(group=Group.CHAT_USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_SYNC = Permission(group=Group.CHAT_USER, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_EDIT = Permission(group=Group.CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_DELETE = Permission(group=Group.CHAT_USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_GROUP = Permission(group=Group.CHAT_USER, operate=Operate.USER_GROUP, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER], label=_('Set up user groups') ) USER_GROUP_READ = Permission(group=Group.USER_GROUP, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) USER_GROUP_CREATE = Permission(group=Group.USER_GROUP, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) USER_GROUP_EDIT = Permission(group=Group.USER_GROUP, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) USER_GROUP_DELETE = Permission(group=Group.USER_GROUP, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) USER_GROUP_ADD_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) USER_GROUP_REMOVE_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_AUTH_READ = Permission(group=Group.CHAT_USER_AUTH, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) CHAT_USER_AUTH_EDIT = Permission(group=Group.CHAT_USER_AUTH, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.CHAT_USER] ) WORKSPACE_CHAT_USER_READ = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_CHAT_USER_CREATE = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_CHAT_USER_EDIT = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_CHAT_USER_DELETE = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_CHAT_USER_GROUP = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.USER_GROUP, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], label=_('Set up user groups') ) WORKSPACE_USER_GROUP_READ = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_USER_GROUP_CREATE = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_USER_GROUP_EDIT = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_USER_GROUP_DELETE = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_USER_GROUP_ADD_MEMBER = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) WORKSPACE_USER_GROUP_REMOVE_MEMBER = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN], parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT] ) SHARED_TOOL_READ = Permission(group=Group.SYSTEM_TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_CREATE = Permission(group=Group.SYSTEM_TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_EDIT = Permission( group=Group.SYSTEM_TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_DELETE = Permission( group=Group.SYSTEM_TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_IMPORT = Permission( group=Group.SYSTEM_TOOL, operate=Operate.IMPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_EXPORT = Permission( group=Group.SYSTEM_TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_TOOL_EXECUTE_RECORD = Permission( group=Group.SYSTEM_TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_READ = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_CREATE = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_SYNC = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_VECTOR = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_EXPORT = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_GENERATE = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DELETE = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_WORKFLOW_READ = Permission( group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_WORKFLOW_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_WORKFLOW_EXPORT = Permission( group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_READ = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_CREATE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_DELETE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_SYNC = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_EXPORT = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_GENERATE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_VECTOR = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_MIGRATE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_TAG = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.TAG, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_DOCUMENT_REPLACE = Permission( group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_TAG_READ = Permission( group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_TAG_CREATE = Permission( group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_TAG_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_TAG_DELETE = Permission( group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_PROBLEM_READ = Permission( group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_PROBLEM_CREATE = Permission( group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_PROBLEM_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_PROBLEM_DELETE = Permission( group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_PROBLEM_RELATE = Permission( group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_HIT_TEST = Permission( group=Group.SYSTEM_KNOWLEDGE_HIT_TEST, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_CHAT_USER_READ = Permission( group=Group.SYSTEM_KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_KNOWLEDGE_CHAT_USER_EDIT = Permission( group=Group.SYSTEM_KNOWLEDGE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE" ) SHARED_MODEL_READ = Permission( group=Group.SYSTEM_MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == "EE" ) SHARED_MODEL_CREATE = Permission( group=Group.SYSTEM_MODEL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == "EE" ) SHARED_MODEL_EDIT = Permission( group=Group.SYSTEM_MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == "EE" ) SHARED_MODEL_DELETE = Permission( group=Group.SYSTEM_MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == "EE" ) SHARED_MODEL_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_READ = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_EDIT = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_DELETE = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_EXPORT = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_AUTH = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_TRIGGER_READ = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_TRIGGER_CREATE = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_TRIGGER_EDIT = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_TRIGGER_DELETE = Permission( group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_READ = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_EMBED = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.EMBED, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_ACCESS = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.ACCESS, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_DISPLAY = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.DISPLAY, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_API_KEY = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.API_KEY, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_OVERVIEW_PUBLIC = Permission( group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.PUBLIC_ACCESS, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) # 应用接入 RESOURCE_APPLICATION_ACCESS_READ = Permission( group=Group.SYSTEM_RES_APPLICATION_ACCESS, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_ACCESS_EDIT = Permission( group=Group.SYSTEM_RES_APPLICATION_ACCESS, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_USER_READ = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_USER_EDIT = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_LOG_READ = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_LOG_ADD_KNOWLEDGE = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.ADD_KNOWLEDGE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_LOG_ANNOTATION = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.ANNOTATION, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_LOG_EXPORT = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) RESOURCE_APPLICATION_CHAT_LOG_CLEAR_POLICY = Permission( group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.CLEAR_POLICY, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == "EE" ) # 知识库 RESOURCE_KNOWLEDGE_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DELETE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_SYNC = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_EXPORT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_VECTOR = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_GENERATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_AUTH = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) # 文档 RESOURCE_KNOWLEDGE_WORKFLOW_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_WORKFLOW_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_CREATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_DELETE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_SYNC = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_EXPORT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_GENERATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_VECTOR = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_MIGRATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_TAG = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.TAG, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_DOCUMENT_REPLACE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_HIT_TEST = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_HIT_TEST, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_PROBLEM_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_PROBLEM_CREATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_PROBLEM_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_PROBLEM_DELETE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_PROBLEM_RELATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_TAG_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_TAG_CREATE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_TAG_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_TAG_DELETE = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_CHAT_USER_READ = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_KNOWLEDGE_CHAT_USER_EDIT = Permission( group=Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_READ = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_EDIT = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_DELETE = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_EXPORT = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_AUTH = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_EXECUTE_RECORD = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_TRIGGER_READ = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_TRIGGER_CREATE = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_TRIGGER_EDIT = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_TOOL_TRIGGER_DELETE = Permission( group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == "EE" ) RESOURCE_MODEL_READ = Permission( group=Group.SYSTEM_RES_MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == "EE" ) RESOURCE_MODEL_EDIT = Permission( group=Group.SYSTEM_RES_MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == "EE" ) RESOURCE_MODEL_DELETE = Permission( group=Group.SYSTEM_RES_MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == "EE" ) RESOURCE_MODEL_AUTH = Permission( group=Group.SYSTEM_RES_MODEL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == "EE" ) RESOURCE_MODEL_RELATE_RESOURCE_VIEW = Permission( group=Group.SYSTEM_RES_MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == "EE" ) OPERATION_LOG_READ = Permission( group=Group.OPERATION_LOG, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.OPERATION_LOG] ) OPERATION_LOG_EXPORT = Permission( group=Group.OPERATION_LOG, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.OPERATION_LOG] ) OPERATION_LOG_CLEAR_POLICY = Permission( group=Group.OPERATION_LOG, operate=Operate.CLEAR_POLICY, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.OPERATION_LOG] ) def get_workspace_application_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}/APPLICATION/{kwargs.get('application_id')}") def get_workspace_knowledge_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}/KNOWLEDGE/{kwargs.get('knowledge_id')}") def get_workspace_model_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}/MODEL/{kwargs.get('model_id')}") def get_workspace_tool_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}/TOOL/{kwargs.get('tool_id')}") def get_workspace_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}") def get_workspace_permission_workspace_manage_role(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/{RoleConstants.WORKSPACE_MANAGE.value.__str__()}") def __eq__(self, other): if isinstance(other, PermissionConstants): return other == self else: return self.value == other def get_default_permission_list_by_role(role: RoleConstants): """ 根据角色 获取角色对应的权限 :param role: 角色 :return: 权限 """ return list(map(lambda k: PermissionConstants[k], list(filter(lambda k: PermissionConstants[k].value.role_list.__contains__(role), PermissionConstants.__members__)))) class RolePermissionMapping: def __init__(self, role_id, permission_id): self.role_id = role_id self.permission_id = permission_id class WorkspaceUserRoleMapping: def __init__(self, workspace_id, role_id, user_id): self.workspace_id = workspace_id self.role_id = role_id self.user_id = user_id def get_default_role_permission_mapping_list(): role_permission_mapping_list = [ [RolePermissionMapping(role.value.name, PermissionConstants[k].value.__str__()) for role in PermissionConstants[k].value.role_list] for k in PermissionConstants.__members__] return reduce(lambda x, y: [*x, *y], role_permission_mapping_list, []) def get_default_workspace_user_role_mapping_list(user_role_list: list): return [WorkspaceUserRoleMapping('default', role.value.name, 'default') for role in RoleConstants if user_role_list.__contains__(role.value.name)] def get_permission_list_by_resource_group(resource_group: ResourcePermissionGroup): """ 根据资源组获取权限 """ return [PermissionConstants[k].value for k in PermissionConstants.__members__ if PermissionConstants[k].value.resource_permission_group_list.__contains__(resource_group)] class ChatAuth: def __init__(self, current_role_list: List[RoleConstants | Role], permission_list: List[PermissionConstants | Permission], chat_user_id, chat_user_type, application_id): # 权限列表 self.permission_list = permission_list # 角色列表 self.role_list = current_role_list self.chat_user_id = chat_user_id self.chat_user_type = chat_user_type self.application_id = application_id class Auth: """ 用于存储当前用户的角色和权限 """ def __init__(self, current_role_list: List[RoleConstants | Role], permission_list: List[PermissionConstants | Permission], **keywords): # 权限列表 self.permission_list = permission_list # 角色列表 self.role_list = current_role_list self.keywords = keywords class CompareConstants(Enum): # 或者 OR = "OR" # 并且 AND = "AND" class ViewPermission: def __init__(self, roleList: List[RoleConstants], permissionList: List[PermissionConstants | object], compare=CompareConstants.OR): self.roleList = roleList self.permissionList = permissionList self.compare = compare ================================================ FILE: apps/common/database_model_manage/__init__.py ================================================ ================================================ FILE: apps/common/database_model_manage/database_model_manage.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: database_model_manage.py @date:2025/4/15 11:06 @desc: """ from importlib import import_module from django.conf import settings def new_instance_by_class_path(class_path: str): """ 根据class_path 创建实例 """ parts = class_path.rpartition('.') package_path = parts[0] class_name = parts[2] module = import_module(package_path) HandlerClass = getattr(module, class_name) return HandlerClass() class DatabaseModelManage: """ 模型字典 """ model_dict = {} @staticmethod def get_model(model_name): """ 根据模型 """ return DatabaseModelManage.model_dict.get(model_name) @staticmethod def init(): handles = [new_instance_by_class_path(class_path) for class_path in (settings.MODEL_HANDLES if hasattr(settings, 'MODEL_HANDLES') else [])] for h in handles: model_dict = h.get_model_dict() DatabaseModelManage.model_dict = {**DatabaseModelManage.model_dict, **model_dict} ================================================ FILE: apps/common/database_model_manage/handle/__init__.py ================================================ ================================================ FILE: apps/common/database_model_manage/handle/base_handle.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_handle.py @date:2025/4/15 11:16 @desc: """ from abc import ABC, abstractmethod class IBaseModelHandle(ABC): @abstractmethod def get_model_dict(self): pass ================================================ FILE: apps/common/database_model_manage/handle/impl/__init__.py ================================================ ================================================ FILE: apps/common/database_model_manage/handle/impl/default_base_model_handle.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: default_base_model_handle.py @date:2025/4/15 11:20 @desc: """ from common.database_model_manage.handle.base_handle import IBaseModelHandle class DefaultBaseModelHandle(IBaseModelHandle): def get_model_dict(self): return {} ================================================ FILE: apps/common/db/__init__.py ================================================ ================================================ FILE: apps/common/db/compiler.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: compiler.py @date:2023/10/7 10:53 @desc: """ from django.core.exceptions import EmptyResultSet, FullResultSet from django.db import NotSupportedError from django.db.models.sql.compiler import SQLCompiler from django.db.transaction import TransactionManagementError class AppSQLCompiler(SQLCompiler): def __init__(self, query, connection, using, elide_empty=True, field_replace_dict=None): super().__init__(query, connection, using, elide_empty) if field_replace_dict is None: field_replace_dict = {} self.field_replace_dict = field_replace_dict def get_query_str(self, with_limits=True, with_table_name=False, with_col_aliases=False): refcounts_before = self.query.alias_refcount.copy() try: combinator = self.query.combinator extra_select, order_by, group_by = self.pre_sql_setup( with_col_aliases=with_col_aliases or bool(combinator), ) for_update_part = None # Is a LIMIT/OFFSET clause needed? with_limit_offset = with_limits and self.query.is_sliced combinator = self.query.combinator features = self.connection.features if combinator: if not getattr(features, "supports_select_{}".format(combinator)): raise NotSupportedError( "{} is not supported on this database backend.".format( combinator ) ) result, params = self.get_combinator_sql( combinator, self.query.combinator_all ) elif self.qualify: result, params = self.get_qualify_sql() order_by = None else: distinct_fields, distinct_params = self.get_distinct() # This must come after 'select', 'ordering', and 'distinct' # (see docstring of get_from_clause() for details). from_, f_params = self.get_from_clause() try: where, w_params = ( self.compile(self.where) if self.where is not None else ("", []) ) except EmptyResultSet: if self.elide_empty: raise # Use a predicate that's always False. where, w_params = "0 = 1", [] except FullResultSet: where, w_params = "", [] try: having, h_params = ( self.compile(self.having) if self.having is not None else ("", []) ) except FullResultSet: having, h_params = "", [] result = [] params = [] if self.query.distinct: distinct_result, distinct_params = self.connection.ops.distinct_sql( distinct_fields, distinct_params, ) result += distinct_result params += distinct_params out_cols = [] for _, (s_sql, s_params), alias in self.select + extra_select: if alias: s_sql = "%s AS %s" % ( s_sql, self.connection.ops.quote_name(alias), ) params.extend(s_params) out_cols.append(s_sql) params.extend(f_params) if self.query.select_for_update and features.has_select_for_update: if ( self.connection.get_autocommit() # Don't raise an exception when database doesn't # support transactions, as it's a noop. and features.supports_transactions ): raise TransactionManagementError( "select_for_update cannot be used outside of a transaction." ) if ( with_limit_offset and not features.supports_select_for_update_with_limit ): raise NotSupportedError( "LIMIT/OFFSET is not supported with " "select_for_update on this database backend." ) nowait = self.query.select_for_update_nowait skip_locked = self.query.select_for_update_skip_locked of = self.query.select_for_update_of no_key = self.query.select_for_no_key_update # If it's a NOWAIT/SKIP LOCKED/OF/NO KEY query but the # backend doesn't support it, raise NotSupportedError to # prevent a possible deadlock. if nowait and not features.has_select_for_update_nowait: raise NotSupportedError( "NOWAIT is not supported on this database backend." ) elif skip_locked and not features.has_select_for_update_skip_locked: raise NotSupportedError( "SKIP LOCKED is not supported on this database backend." ) elif of and not features.has_select_for_update_of: raise NotSupportedError( "FOR UPDATE OF is not supported on this database backend." ) elif no_key and not features.has_select_for_no_key_update: raise NotSupportedError( "FOR NO KEY UPDATE is not supported on this " "database backend." ) for_update_part = self.connection.ops.for_update_sql( nowait=nowait, skip_locked=skip_locked, of=self.get_select_for_update_of_arguments(), no_key=no_key, ) if for_update_part and features.for_update_after_from: result.append(for_update_part) if where: result.append("WHERE %s" % where) params.extend(w_params) grouping = [] for g_sql, g_params in group_by: grouping.append(g_sql) params.extend(g_params) if grouping: if distinct_fields: raise NotImplementedError( "annotate() + distinct(fields) is not implemented." ) order_by = order_by or self.connection.ops.force_no_ordering() result.append("GROUP BY %s" % ", ".join(grouping)) if self._meta_ordering: order_by = None if having: result.append("HAVING %s" % having) params.extend(h_params) if self.query.explain_info: result.insert( 0, self.connection.ops.explain_query_prefix( self.query.explain_info.format, **self.query.explain_info.options, ), ) if order_by: ordering = [] for _, (o_sql, o_params, _) in order_by: ordering.append(o_sql) params.extend(o_params) order_by_sql = "ORDER BY %s" % ", ".join(ordering) if combinator and features.requires_compound_order_by_subquery: result = ["SELECT * FROM (", *result, ")", order_by_sql] else: result.append(order_by_sql) if with_limit_offset: result.append( self.connection.ops.limit_offset_sql( self.query.low_mark, self.query.high_mark ) ) if for_update_part and not features.for_update_after_from: result.append(for_update_part) from_, f_params = self.get_from_clause() sql = " ".join(result) if not with_table_name: for table_name in from_: sql = sql.replace(table_name + ".", "") for key in self.field_replace_dict.keys(): value = self.field_replace_dict.get(key) sql = sql.replace(key, value) return sql, tuple(params) finally: # Finally do cleanup - get rid of the joins we created above. self.query.reset_refcounts(refcounts_before) def as_sql(self, with_limits=True, with_col_aliases=False, select_string=None): if select_string is None: return super().as_sql(with_limits, with_col_aliases) else: sql, params = self.get_query_str(with_table_name=False) return (select_string + " " + sql), params ================================================ FILE: apps/common/db/search.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: search.py @date:2023/10/7 18:20 @desc: """ import hashlib from typing import Dict, Any from django.db import DEFAULT_DB_ALIAS, models, connections from django.db.models import QuerySet from common.db.compiler import AppSQLCompiler from common.db.sql_execute import select_one, select_list, update_execute from common.result import Page # 添加模型缓存 _model_cache = {} def get_dynamics_model(attr: dict, table_name='dynamics'): """ 获取一个动态的django模型 :param attr: 模型字段 :param table_name: 表名 :return: django 模型 """ # 创建缓存键,基于属性和表名 cache_key = hashlib.md5(f"{table_name}_{str(sorted(attr.items()))}".encode()).hexdigest() # print(f'cache_key: {cache_key}') # 如果模型已存在,直接返回缓存的模型 if cache_key in _model_cache: return _model_cache[cache_key] attributes = { "__module__": "knowledge.models", "Meta": type("Meta", (), {'db_table': table_name}), **attr } # 使用唯一的类名避免冲突 class_name = f'Dynamics_{cache_key[:8]}' model_class = type(class_name, (models.Model,), attributes) # 缓存模型 _model_cache[cache_key] = model_class return model_class def generate_sql_by_query_dict(queryset_dict: Dict[str, QuerySet], select_string: str, field_replace_dict: None | Dict[str, Dict[str, str]] = None, with_table_name=False): """ 生成 查询sql :param with_table_name: :param queryset_dict: 多条件 查询条件 :param select_string: 查询sql :param field_replace_dict: 需要替换的查询字段,一般不需要传入如果有特殊的需要传入 :return: sql:需要查询的sql params: sql 参数 """ params_dict: Dict[int, Any] = {} result_params = [] for key in queryset_dict.keys(): value = queryset_dict.get(key) sql, params = compiler_queryset(value, None if field_replace_dict is None else field_replace_dict.get(key), with_table_name) params_dict = {**params_dict, select_string.index("${" + key + "}"): params} select_string = select_string.replace("${" + key + "}", sql) for key in sorted(list(params_dict.keys())): result_params = [*result_params, *params_dict.get(key)] return select_string, result_params def generate_sql_by_query(queryset: QuerySet, select_string: str, field_replace_dict: None | Dict[str, str] = None, with_table_name=False): """ 生成 查询sql :param queryset: 查询条件 :param select_string: 原始sql :param field_replace_dict: 需要替换的查询字段,一般不需要传入如果有特殊的需要传入 :return: sql:需要查询的sql params: sql 参数 """ sql, params = compiler_queryset(queryset, field_replace_dict, with_table_name) return select_string + " " + sql, params def compiler_queryset(queryset: QuerySet, field_replace_dict: None | Dict[str, str] = None, with_table_name=False): """ 解析 queryset查询对象 :param with_table_name: :param queryset: 查询对象 :param field_replace_dict: 需要替换的查询字段,一般不需要传入如果有特殊的需要传入 :return: sql:需要查询的sql params: sql 参数 """ q = queryset.query compiler = q.get_compiler(DEFAULT_DB_ALIAS) if field_replace_dict is None: field_replace_dict = get_field_replace_dict(queryset) app_sql_compiler = AppSQLCompiler(q, using=DEFAULT_DB_ALIAS, connection=compiler.connection, field_replace_dict=field_replace_dict) sql, params = app_sql_compiler.get_query_str(with_table_name=with_table_name) return sql, params def native_search(queryset: QuerySet | Dict[str, QuerySet], select_string: str, field_replace_dict: None | Dict[str, Dict[str, str]] | Dict[str, str] = None, with_search_one=False, with_table_name=False): """ 复杂查询 :param with_table_name: 生成sql是否包含表名 :param queryset: 查询条件构造器 :param select_string: 查询前缀 不包括 where limit 等信息 :param field_replace_dict: 需要替换的字段 :param with_search_one: 查询 :return: 查询结果 """ if isinstance(queryset, Dict): exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name) else: exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name) if with_search_one: return select_one(exec_sql, exec_params) else: return select_list(exec_sql, exec_params) def native_update(queryset: QuerySet | Dict[str, QuerySet], select_string: str, field_replace_dict: None | Dict[str, Dict[str, str]] | Dict[str, str] = None, with_table_name=False): """ 复杂查询 :param with_table_name: 生成sql是否包含表名 :param queryset: 查询条件构造器 :param select_string: 查询前缀 不包括 where limit 等信息 :param field_replace_dict: 需要替换的字段 :return: 查询结果 """ if isinstance(queryset, Dict): exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name) else: exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name) return update_execute(exec_sql, exec_params) def page_search(current_page: int, page_size: int, queryset: QuerySet, post_records_handler): """ 分页查询 :param current_page: 当前页 :param page_size: 每页大小 :param queryset: 查询条件 :param post_records_handler: 数据处理器 :return: 分页结果 """ total = QuerySet(query=queryset.query.clone(), model=queryset.model).count() result = queryset.all()[((current_page - 1) * page_size):(current_page * page_size)] return Page(total, list(map(post_records_handler, result)), current_page, page_size) def native_page_search(current_page: int, page_size: int, queryset: QuerySet | Dict[str, QuerySet], select_string: str, field_replace_dict=None, post_records_handler=lambda r: r, with_table_name=False): """ 复杂分页查询 :param with_table_name: :param current_page: 当前页 :param page_size: 每页大小 :param queryset: 查询条件 :param select_string: 查询 :param field_replace_dict: 特殊字段替换 :param post_records_handler: 数据row处理器 :return: 分页结果 """ if isinstance(queryset, Dict): exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name) else: exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name) total_sql = "SELECT \"count\"(*) FROM (%s) temp" % exec_sql total = select_one(total_sql, exec_params) limit_sql = connections[DEFAULT_DB_ALIAS].ops.limit_offset_sql( ((current_page - 1) * page_size), (current_page * page_size) ) page_sql = exec_sql + " " + limit_sql result = select_list(page_sql, exec_params) return Page(total.get("count"), list(map(post_records_handler, result)), current_page, page_size) def native_page_handler(page_size: int, queryset: QuerySet | Dict[str, QuerySet], select_string: str, field_replace_dict=None, with_table_name=False, primary_key=None, get_primary_value=None, primary_queryset: str = None, ): if isinstance(queryset, Dict): exec_sql, exec_params = generate_sql_by_query_dict({**queryset, primary_queryset: queryset[primary_queryset].order_by( primary_key)}, select_string, field_replace_dict, with_table_name) else: exec_sql, exec_params = generate_sql_by_query(queryset.order_by( primary_key), select_string, field_replace_dict, with_table_name) total_sql = "SELECT \"count\"(*) FROM (%s) temp" % exec_sql total = select_one(total_sql, exec_params) processed_count = 0 last_id = None while processed_count < total.get("count"): if last_id is not None: if isinstance(queryset, Dict): exec_sql, exec_params = generate_sql_by_query_dict({**queryset, primary_queryset: queryset[primary_queryset].filter( **{f"{primary_key}__gt": last_id}).order_by( primary_key)}, select_string, field_replace_dict, with_table_name) else: exec_sql, exec_params = generate_sql_by_query( queryset.filter(**{f"{primary_key}__gt": last_id}).order_by( primary_key), select_string, field_replace_dict, with_table_name) limit_sql = connections[DEFAULT_DB_ALIAS].ops.limit_offset_sql( 0, page_size ) page_sql = exec_sql + " " + limit_sql result = select_list(page_sql, exec_params) yield result processed_count += page_size last_id = get_primary_value(result[-1]) def get_field_replace_dict(queryset: QuerySet): """ 获取需要替换的字段 默认 “xxx.xxx”需要被替换成 “xxx”."xxx" :param queryset: 查询对象 :return: 需要替换的字典 """ result = {} for field in queryset.model._meta.local_fields: if field.attname.__contains__("."): replace_field = to_replace_field(field.attname) result.__setitem__('"' + field.attname + '"', replace_field) return result def to_replace_field(field: str): """ 将field 转换为 需要替换的field “xxx.xxx”需要被替换成 “xxx”."xxx" 只替换 field包含.的字段 :param field: django field字段 :return: 替换字段 """ split_field = field.split(".") return ".".join(list(map(lambda sf: '"' + sf + '"', split_field))) ================================================ FILE: apps/common/db/sql_execute.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: sql_execute.py @date:2023/9/25 20:05 @desc: """ from typing import List from django.db import connection def sql_execute(sql: str, params): """ 执行一条sql :param sql: 需要执行的sql :param params: sql参数 :return: 执行结果 """ with connection.cursor() as cursor: cursor.execute(sql, params) columns = list(map(lambda d: d.name, cursor.description)) res = cursor.fetchall() result = list(map(lambda row: dict(list(zip(columns, row))), res)) cursor.close() return result def update_execute(sql: str, params): """ 执行一条sql :param sql: 需要执行的sql :param params: sql参数 :return: 执行结果 """ with connection.cursor() as cursor: cursor.execute(sql, params) affected_rows = cursor.rowcount cursor.close() return affected_rows def select_list(sql: str, params: List): """ 执行sql 查询列表数据 :param sql: 需要执行的sql :param params: sql的参数 :return: 查询结果 """ result_list = sql_execute(sql, params) if result_list is None: return [] return result_list def select_one(sql: str, params: List): """ 执行sql 查询一条数据 :param sql: 需要执行的sql :param params: 参数 :return: 查询结果 """ result_list = sql_execute(sql, params) if result_list is None or len(result_list) == 0: return None return result_list[0] ================================================ FILE: apps/common/encoder/__init__.py ================================================ ================================================ FILE: apps/common/encoder/encoder.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: SystemEncoder.py @date:2025/3/17 16:38 @desc: """ import datetime import decimal import json import uuid from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile class SystemEncoder(json.JSONEncoder): def encode(self, obj): # 先序列化为字符串 json_str = super().encode(obj) # 移除所有空字符 json_str = json_str.replace('\\u0000', '') return json_str def default(self, obj): if isinstance(obj, uuid.UUID): 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) if isinstance(obj, InMemoryUploadedFile): return {'name': obj.name, 'size': obj.size} if isinstance(obj, TemporaryUploadedFile): return {'name': obj.name, 'size': obj.size} else: return json.JSONEncoder.default(self, obj) ================================================ FILE: apps/common/event/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2023/11/10 10:43 @desc: """ from django.core.cache import cache from django.db.models import QuerySet from django.utils.translation import gettext as _ from ..constants.cache_version import Cache_Version from ..db.sql_execute import update_execute from ..utils.lock import RedisLock update_document_status_sql = """ UPDATE "public"."document" SET status ="replace"("replace"("replace"(status, '1', '3'), '0', '3'), '4', '3') WHERE status ~ '1|0|4' \ """ def run(): from models_provider.models import Model, Status rlock = RedisLock() if rlock.try_lock('event_init', 30 * 30): try: # 修改Model状态为ERROR QuerySet(Model).filter( status=Status.DOWNLOAD ).update( status=Status.ERROR, meta={'message': _('The download process was interrupted, please try again')} ) # 更新文档状态 update_execute(update_document_status_sql, []) version, get_key = Cache_Version.SYSTEM.value cache.delete(get_key(key='rsa_key'), version=version) finally: rlock.un_lock('event_init') ================================================ FILE: apps/common/event/common.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: common.py @date:2023/11/10 10:41 @desc: """ from concurrent.futures import ThreadPoolExecutor from django.core.cache.backends.locmem import LocMemCache work_thread_pool = ThreadPoolExecutor(5) embedding_thread_pool = ThreadPoolExecutor(3) memory_cache = LocMemCache('task', {"OPTIONS": {"MAX_ENTRIES": 1000}}) def poxy(poxy_function): def inner(args, **keywords): work_thread_pool.submit(poxy_function, args, **keywords) return inner def get_cache_key(poxy_function, args): return poxy_function.__name__ + str(args) def get_cache_poxy_function(poxy_function, cache_key): def fun(args, **keywords): try: poxy_function(args, **keywords) finally: memory_cache.delete(cache_key) return fun def embedding_poxy(poxy_function): def inner(*args, **keywords): key = get_cache_key(poxy_function, args) if memory_cache.has_key(key): return memory_cache.add(key, None) f = get_cache_poxy_function(poxy_function, key) embedding_thread_pool.submit(f, args, **keywords) return inner ================================================ FILE: apps/common/event/listener_manage.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: listener_manage.py @date:2023/10/20 14:01 @desc: """ import datetime import os import threading import traceback from typing import List import django.db.models from django.db.models import QuerySet from django.db.models.functions import Substr, Reverse from django.utils.translation import gettext_lazy as _ from django.utils import timezone from langchain_core.embeddings import Embeddings from common.config.embedding_config import VectorStore from common.db.search import native_search, get_dynamics_model, native_update from common.utils.common import get_file_content from common.utils.lock import RedisLock from common.utils.logger import maxkb_logger from common.utils.page_utils import page_desc from knowledge.models import Paragraph, Status, Document, ProblemParagraphMapping, TaskType, State, SourceType, \ SearchMode from knowledge.serializers.common import create_knowledge_index from maxkb.conf import (PROJECT_DIR) lock = threading.Lock() class SyncWebKnowledgeArgs: def __init__(self, lock_key: str, url: str, selector: str, handler): self.lock_key = lock_key self.url = url self.selector = selector self.handler = handler class SyncWebDocumentArgs: def __init__(self, source_url_list: List[str], selector: str, handler): self.source_url_list = source_url_list self.selector = selector self.handler = handler class UpdateProblemArgs: def __init__(self, problem_id: str, problem_content: str, embedding_model: Embeddings): self.problem_id = problem_id self.problem_content = problem_content self.embedding_model = embedding_model class UpdateEmbeddingKnowledgeIdArgs: def __init__(self, paragraph_id_list: List[str], target_knowledge_id: str): self.paragraph_id_list = paragraph_id_list self.target_knowledge_id = target_knowledge_id class UpdateEmbeddingDocumentIdArgs: def __init__(self, paragraph_id_list: List[str], target_document_id: str, target_knowledge_id: str, target_embedding_model: Embeddings = None): self.paragraph_id_list = paragraph_id_list self.target_document_id = target_document_id self.target_knowledge_id = target_knowledge_id self.target_embedding_model = target_embedding_model class ListenerManagement: @staticmethod def embedding_by_problem(args, embedding_model: Embeddings): VectorStore.get_embedding_vector().save(**args, embedding=embedding_model) @staticmethod def embedding_by_paragraph_list(paragraph_id_list, embedding_model: Embeddings): try: data_list = native_search( {'problem': QuerySet(get_dynamics_model({'paragraph.id': django.db.models.CharField()})).filter( **{'paragraph.id__in': paragraph_id_list}), 'paragraph': QuerySet(Paragraph).filter(id__in=paragraph_id_list)}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql'))) ListenerManagement.embedding_by_paragraph_data_list(data_list, paragraph_id_list=paragraph_id_list, embedding_model=embedding_model) except Exception as e: maxkb_logger.error(_('Query vector data: {paragraph_id_list} error {error} {traceback}').format( paragraph_id_list=paragraph_id_list, error=str(e), traceback=traceback.format_exc())) @staticmethod def embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model: Embeddings): maxkb_logger.info(_('Start--->Embedding paragraph: {paragraph_id_list}').format( paragraph_id_list=paragraph_id_list) ) try: # 删除段落 VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_id_list) def is_save_function(): return QuerySet(Paragraph).filter(id__in=paragraph_id_list).exists() # 批量向量化 VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_save_function) ListenerManagement.update_status( QuerySet(Paragraph).filter(id__in=paragraph_id_list), TaskType.EMBEDDING, State.SUCCESS ) except Exception as e: maxkb_logger.error(_('Vectorized paragraph: {paragraph_id_list} error {error} {traceback}').format( paragraph_id_list=paragraph_id_list, error=str(e), traceback=traceback.format_exc()) ) ListenerManagement.update_status( QuerySet(Paragraph).filter(id__in=paragraph_id_list), TaskType.EMBEDDING, State.FAILURE ) finally: maxkb_logger.info(_('End--->Embedding paragraph: {paragraph_id_list}').format( paragraph_id_list=paragraph_id_list) ) @staticmethod def embedding_by_paragraph(paragraph_id, embedding_model: Embeddings): """ 向量化段落 根据段落id @param paragraph_id: 段落id @param embedding_model: 向量模型 """ maxkb_logger.info(_('Start--->Embedding paragraph: {paragraph_id}').format(paragraph_id=paragraph_id)) # 更新到开始状态 ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING, State.STARTED) try: data_list = native_search( {'problem': QuerySet(get_dynamics_model({'paragraph.id': django.db.models.CharField()})).filter( **{'paragraph.id': paragraph_id}), 'paragraph': QuerySet(Paragraph).filter(id=paragraph_id)}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql'))) # 删除段落 VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id) def is_the_task_interrupted(): _paragraph = QuerySet(Paragraph).filter(id=paragraph_id).first() if _paragraph is None or Status(_paragraph.status)[TaskType.EMBEDDING] == State.REVOKE: return True return False # 批量向量化 VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_the_task_interrupted) # 更新到开始状态 ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING, State.SUCCESS) except Exception as e: maxkb_logger.error(_('Vectorized paragraph: {paragraph_id} error {error} {traceback}').format( paragraph_id=paragraph_id, error=str(e), traceback=traceback.format_exc())) ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING, State.FAILURE) finally: maxkb_logger.info(_('End--->Embedding paragraph: {paragraph_id}').format(paragraph_id=paragraph_id)) @staticmethod def embedding_by_data_list(data_list: List, embedding_model: Embeddings): # 批量向量化 VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, lambda: False) @staticmethod def get_embedding_paragraph_apply(embedding_model, is_the_task_interrupted, post_apply=lambda: None): def embedding_paragraph_apply(paragraph_list): for paragraph in paragraph_list: if is_the_task_interrupted(): break ListenerManagement.embedding_by_paragraph(str(paragraph.get('id')), embedding_model) post_apply() return embedding_paragraph_apply @staticmethod def get_aggregation_document_status(document_id): def aggregation_document_status(): sql = get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'update_document_status_meta.sql')) native_update({'document_custom_sql': QuerySet(Document).filter(id=document_id)}, sql, with_table_name=True) return aggregation_document_status @staticmethod def get_aggregation_document_status_by_knowledge_id(knowledge_id): def aggregation_document_status(): sql = get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'update_document_status_meta.sql')) native_update({'document_custom_sql': QuerySet(Document).filter(knowledge_id=knowledge_id)}, sql, with_table_name=True) return aggregation_document_status @staticmethod def get_aggregation_document_status_by_query_set(queryset): def aggregation_document_status(): sql = get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'update_document_status_meta.sql')) native_update({'document_custom_sql': queryset}, sql, with_table_name=True) return aggregation_document_status @staticmethod def post_update_document_status(document_id, task_type: TaskType): _document = QuerySet(Document).filter(id=document_id).first() status = Status(_document.status) if status[task_type] == State.REVOKE: status[task_type] = State.REVOKED else: status[task_type] = State.SUCCESS for item in _document.status_meta.get('aggs', []): agg_status = item.get('status') agg_count = item.get('count') if Status(agg_status)[task_type] == State.FAILURE and agg_count > 0: status[task_type] = State.FAILURE ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), task_type, status[task_type]) ListenerManagement.update_status(QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', task_type.value, task_type.value), ).filter(task_type_status=State.REVOKE.value).filter(document_id=document_id).values('id'), task_type, State.REVOKED) @staticmethod def update_status(query_set: QuerySet, taskType: TaskType, state: State): exec_sql = get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'update_paragraph_status.sql')) bit_number = len(TaskType) up_index = taskType.value - 1 next_index = taskType.value + 1 current_index = taskType.value status_number = state.value current_time = timezone.now().astimezone(timezone.get_default_timezone()).strftime('%Y-%m-%d %H:%M:%S.%f%z') params_dict = {'${bit_number}': bit_number, '${up_index}': up_index, '${status_number}': status_number, '${next_index}': next_index, '${table_name}': query_set.model._meta.db_table, '${current_index}': current_index, '${current_time}': current_time} for key in params_dict: _value_ = params_dict[key] exec_sql = exec_sql.replace(key, str(_value_)) lock.acquire() try: native_update(query_set, exec_sql) finally: lock.release() @staticmethod def embedding_by_document(document_id, embedding_model: Embeddings, state_list=None): """ 向量化文档 @param state_list: @param document_id: 文档id @param embedding_model 向量模型 :return: None """ if state_list is None: state_list = [State.PENDING, State.SUCCESS, State.FAILURE, State.REVOKE, State.REVOKED] rlock = RedisLock() if not rlock.try_lock('embedding:' + str(document_id)): return try: def is_the_task_interrupted(): document = QuerySet(Document).filter(id=document_id).first() if document is None or Status(document.status)[TaskType.EMBEDDING] == State.REVOKE: return True return False if is_the_task_interrupted(): return maxkb_logger.info(_('Start--->Embedding document: {document_id}').format(document_id=document_id) ) # 批量修改状态为PADDING ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.STARTED) # 根据段落进行向量化处理 page_desc(QuerySet(Paragraph) .annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType.EMBEDDING.value, 1), ).filter(task_type_status__in=state_list, document_id=document_id) .values('id'), 5, ListenerManagement.get_embedding_paragraph_apply(embedding_model, is_the_task_interrupted, ListenerManagement.get_aggregation_document_status( document_id)), is_the_task_interrupted) # 检查是否存在索引 create_knowledge_index(document_id=document_id) except Exception as e: maxkb_logger.error(_('Vectorized document: {document_id} error {error} {traceback}').format( document_id=document_id, error=str(e), traceback=traceback.format_exc())) finally: ListenerManagement.post_update_document_status(document_id, TaskType.EMBEDDING) ListenerManagement.get_aggregation_document_status(document_id)() maxkb_logger.info(_('End--->Embedding document: {document_id}').format(document_id=document_id)) rlock.un_lock('embedding:' + str(document_id)) @staticmethod def embedding_by_knowledge(knowledge_id, embedding_model: Embeddings): """ 向量化知识库 @param knowledge_id: 知识库id @param embedding_model 向量模型 :return: None """ maxkb_logger.info(_('Start--->Embedding knowledge: {knowledge_id}').format(knowledge_id=knowledge_id)) try: ListenerManagement.delete_embedding_by_knowledge(knowledge_id) document_list = QuerySet(Document).filter(knowledge_id=knowledge_id) maxkb_logger.info(_('Start--->Embedding document: {document_list}').format(document_list=document_list)) for document in document_list: ListenerManagement.embedding_by_document(document.id, embedding_model=embedding_model) except Exception as e: maxkb_logger.error(_('Vectorized knowledge: {knowledge_id} error {error} {traceback}').format( knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc())) finally: maxkb_logger.info(_('End--->Embedding knowledge: {knowledge_id}').format(knowledge_id=knowledge_id)) @staticmethod def delete_embedding_by_document(document_id): VectorStore.get_embedding_vector().delete_by_document_id(document_id) @staticmethod def delete_embedding_by_document_list(document_id_list: List[str]): VectorStore.get_embedding_vector().delete_by_document_id_list(document_id_list) @staticmethod def delete_embedding_by_knowledge(knowledge_id): VectorStore.get_embedding_vector().delete_by_knowledge_id(knowledge_id) @staticmethod def delete_embedding_by_paragraph(paragraph_id): VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id) @staticmethod def delete_embedding_by_source(source_id): VectorStore.get_embedding_vector().delete_by_source_id(source_id, SourceType.PROBLEM) @staticmethod def disable_embedding_by_paragraph(paragraph_id): VectorStore.get_embedding_vector().update_by_paragraph_id(paragraph_id, {'is_active': False}) @staticmethod def enable_embedding_by_paragraph(paragraph_id): VectorStore.get_embedding_vector().update_by_paragraph_id(paragraph_id, {'is_active': True}) @staticmethod def update_problem(args: UpdateProblemArgs): problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(problem_id=args.problem_id) embed_value = args.embedding_model.embed_query(args.problem_content) VectorStore.get_embedding_vector().update_by_source_ids([v.id for v in problem_paragraph_mapping_list], {'embedding': embed_value}) @staticmethod def update_embedding_knowledge_id(args: UpdateEmbeddingKnowledgeIdArgs): VectorStore.get_embedding_vector().update_by_paragraph_ids(args.paragraph_id_list, {'knowledge_id': args.target_knowledge_id}) @staticmethod def update_embedding_document_id(args: UpdateEmbeddingDocumentIdArgs): if args.target_embedding_model is None: VectorStore.get_embedding_vector().update_by_paragraph_ids(args.paragraph_id_list, {'document_id': args.target_document_id, 'knowledge_id': args.target_knowledge_id}) else: ListenerManagement.embedding_by_paragraph_list(args.paragraph_id_list, embedding_model=args.target_embedding_model) @staticmethod def delete_embedding_by_source_ids(source_ids: List[str]): VectorStore.get_embedding_vector().delete_by_source_ids(source_ids, SourceType.PROBLEM) @staticmethod def delete_embedding_by_paragraph_ids(paragraph_ids: List[str]): VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_ids) @staticmethod def delete_embedding_by_knowledge_id_list(source_ids: List[str]): VectorStore.get_embedding_vector().delete_by_knowledge_id_list(source_ids) @staticmethod def hit_test(query_text, knowledge_id: list[str], exclude_document_id_list: list[str], top_number: int, similarity: float, search_mode: SearchMode, embedding: Embeddings): return VectorStore.get_embedding_vector().hit_test(query_text, knowledge_id, exclude_document_id_list, top_number, similarity, search_mode, embedding) ================================================ FILE: apps/common/exception/__init__.py ================================================ ================================================ FILE: apps/common/exception/app_exception.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎虎 @file: app_exception.py @date:2023/9/4 14:04 @desc: """ from rest_framework import status class AppApiException(Exception): """ 项目内异常 """ status_code = status.HTTP_200_OK def __init__(self, code, message): self.code = code self.message = message class NotFound404(AppApiException): """ 未认证(未登录)异常 """ status_code = status.HTTP_404_NOT_FOUND def __init__(self, code, message): self.code = code self.message = message class AppAuthenticationFailed(AppApiException): """ 未认证(未登录)异常 """ status_code = status.HTTP_401_UNAUTHORIZED def __init__(self, code, message): self.code = code self.message = message class AppUnauthorizedFailed(AppApiException): """ 未授权(没有权限)异常 """ status_code = status.HTTP_403_FORBIDDEN def __init__(self, code, message): self.code = code self.message = message class AppEmbedIdentityFailed(AppApiException): """ 嵌入cookie异常 """ status_code = 460 def __init__(self, code, message): self.code = code self.message = message class AppChatNumOutOfBoundsFailed(AppApiException): """ 访问次数超过今日访问量 """ status_code = 461 def __init__(self, code, message): self.code = code self.message = message class ChatException(AppApiException): status_code = 500 def __init__(self, code, message): self.code = code self.message = message ================================================ FILE: apps/common/exception/handle_exception.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎虎 @file: handle_exception.py @date:2023/9/5 19:29 @desc: """ import logging import traceback from rest_framework.exceptions import ValidationError, ErrorDetail, APIException from rest_framework.utils.serializer_helpers import ReturnDict from rest_framework.views import exception_handler from common import result from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ from common.utils.logger import maxkb_logger def to_result(key, args, parent_key=None): """ 将校验异常 args转换为统一数据 :param key: 校验key :param args: 校验异常参数 :param parent_key 父key :return: 接口响应对象 """ error_detail = list(filter( lambda d: True if isinstance(d, ErrorDetail) else True if isinstance(d, dict) and len( d.keys()) > 0 else False, (args[0] if len(args) > 0 else {key: [ErrorDetail(_('Unknown exception'), code='unknown')]}).get(key)))[0] if isinstance(error_detail, dict): return list(map(lambda k: to_result(k, args=[error_detail], parent_key=key if parent_key is None else parent_key + '.' + key), error_detail.keys() if len(error_detail) > 0 else []))[0] return result.Result(500 if isinstance(error_detail.code, str) else error_detail.code, message=f"【{key if parent_key is None else parent_key + '.' + key}】为必填参数" if str( error_detail) == "This field is required." else error_detail) def validation_error_to_result(exc: ValidationError): """ 校验异常转响应对象 :param exc: 校验异常 :return: 接口响应对象 """ try: v = find_err_detail(exc.detail) if v is None: return result.error(str(exc.detail)) return result.error(str(v)) except Exception as e: return result.error(str(exc.detail)) def find_err_detail(exc_detail): if isinstance(exc_detail, ErrorDetail): return exc_detail if isinstance(exc_detail, dict): keys = exc_detail.keys() for key in keys: _label = get_label(key, exc_detail) _value = exc_detail[key] if isinstance(_value, list): for v in _value: r = find_err_detail(ReturnDict({key: v}, serializer=exc_detail.serializer)) if r is not None: return r if isinstance(_value, ErrorDetail): return f"{_label}:{find_err_detail(_value)}" if isinstance(_value, dict) and len(_value.keys()) > 0: try: return find_err_detail(ReturnDict(_value, serializer=exc_detail.serializer.fields[key])) except Exception as e: return _value if isinstance(exc_detail, list): for v in exc_detail: r = find_err_detail(ReturnDict(v, serializer=exc_detail.serializer.child)) if r is not None: return r def get_label(key, exc_detail): try: return exc_detail.serializer.fields[key].label except Exception as e: return key def handle_exception(exc, context): exception_class = exc.__class__ # 先调用REST framework默认的异常处理方法获得标准错误响应对象 response = exception_handler(exc, context) # 在此处补充自定义的异常处理 if issubclass(exception_class, ValidationError): return validation_error_to_result(exc) if issubclass(exception_class, AppApiException): return result.Result(exc.code, exc.message, response_status=exc.status_code) if issubclass(exception_class, APIException): return result.error(exc.detail) if response is None: maxkb_logger.error(f'{str(exc)}:{traceback.format_exc()}') return result.error(str(exc)) return response ================================================ FILE: apps/common/field/__init__.py ================================================ ================================================ FILE: apps/common/field/common.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: common.py @date:2024/1/11 18:44 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from django.utils.translation import gettext_lazy as _ class ObjectField(serializers.Field): def __init__(self, model_type_list, **kwargs): self.model_type_list = model_type_list super().__init__(**kwargs) def to_internal_value(self, data): for model_type in self.model_type_list: if isinstance(data, model_type): return data self.fail(_('Message type error'), value=data) def to_representation(self, value): return value class InstanceField(serializers.Field): def __init__(self, model_type, **kwargs): self.model_type = model_type super().__init__(**kwargs) def to_internal_value(self, data): if not isinstance(data, self.model_type): self.fail(_('Message type error'), value=data) return data def to_representation(self, value): return value class FunctionField(serializers.Field): def to_internal_value(self, data): if not callable(data): self.fail(_('not a function'), value=data) return data def to_representation(self, value): return value @extend_schema_field(OpenApiTypes.BINARY) class UploadedImageField(serializers.ImageField): def __init__(self, **kwargs): super().__init__(**kwargs) def to_representation(self, value): return value class UploadedFileField(serializers.FileField): def __init__(self, **kwargs): super().__init__(**kwargs) def to_representation(self, value): return value ================================================ FILE: apps/common/forms/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2023/10/31 17:56 @desc: """ from .array_object_card import * from .base_field import * from .base_form import * from .multi_select import * from .object_card import * from .password_input import * from .radio_field import * from .single_select_field import * from .tab_card import * from .table_radio import * from .text_input_field import * from .radio_button_field import * from .table_checkbox import * from .radio_card_field import * from .label import * from .slider_field import * ================================================ FILE: apps/common/forms/array_object_card.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: array_object_card.py @date:2023/10/31 18:03 @desc: """ from typing import Dict from common.forms.base_field import BaseExecField, TriggerType class ArrayCard(BaseExecField): """ 收集List[Object] """ def __init__(self, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("ArrayObjectCard", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) ================================================ FILE: apps/common/forms/base_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_field.py @date:2023/10/31 18:07 @desc: """ from enum import Enum from typing import List, Dict from common.exception.app_exception import AppApiException from common.forms.label.base_label import BaseLabel from django.utils.translation import gettext_lazy as _ class TriggerType(Enum): # 执行函数获取 OptionList数据 OPTION_LIST = 'OPTION_LIST' # 执行函数获取子表单 CHILD_FORMS = 'CHILD_FORMS' class BaseField: def __init__(self, input_type: str, label: str or BaseLabel, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): """ :param input_type: 字段 :param label: 提示 :param default_value: 默认值 :param relation_show_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示 :param relation_trigger_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据 :param trigger_type: 执行器类型 OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单 :param attrs: 前端attr数据 :param props_info: 其他额外信息 """ if props_info is None: props_info = {} if attrs is None: attrs = {} self.label = label self.attrs = attrs self.props_info = props_info self.default_value = default_value self.input_type = input_type self.relation_show_field_dict = {} if relation_show_field_dict is None else relation_show_field_dict self.relation_trigger_field_dict = [] if relation_trigger_field_dict is None else relation_trigger_field_dict self.required = required self.trigger_type = trigger_type def is_valid(self, value): field_label = self.label.label if hasattr(self.label, 'to_dict') else self.label if self.required and value is None: raise AppApiException(500, _('The field {field_label} is required').format(field_label=field_label)) def to_dict(self, **kwargs): return { 'input_type': self.input_type, 'label': self.label.to_dict(**kwargs) if hasattr(self.label, 'to_dict') else self.label, 'required': self.required, 'default_value': self.default_value, 'relation_show_field_dict': self.relation_show_field_dict, 'relation_trigger_field_dict': self.relation_trigger_field_dict, 'trigger_type': self.trigger_type.value, 'attrs': self.attrs, 'props_info': self.props_info, **kwargs } class BaseDefaultOptionField(BaseField): def __init__(self, input_type: str, label: str, text_field: str, value_field: str, option_list: List[dict], required: bool = False, default_value: object = None, relation_show_field_dict: Dict[str, object] = None, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): """ :param input_type: 字段 :param label: label :param text_field: 文本字段 :param value_field: 值字段 :param option_list: 可选列表 :param required: 是否必填 :param default_value: 默认值 :param relation_show_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示 :param attrs: 前端attr数据 :param props_info: 其他额外信息 """ super().__init__(input_type, label, required, default_value, relation_show_field_dict, {}, TriggerType.OPTION_LIST, attrs, props_info) self.text_field = text_field self.value_field = value_field self.option_list = option_list def to_dict(self, **kwargs): return {**super().to_dict(**kwargs), 'text_field': self.text_field, 'value_field': self.value_field, 'option_list': self.option_list} class BaseExecField(BaseField): def __init__(self, input_type: str, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): """ :param input_type: 字段 :param label: 提示 :param text_field: 文本字段 :param value_field: 值字段 :param provider: 指定供应商 :param method: 执行供应商函数 method :param required: 是否必填 :param default_value: 默认值 :param relation_show_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示 :param relation_trigger_field_dict: {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据 :param trigger_type: 执行器类型 OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单 :param attrs: 前端attr数据 :param props_info: 其他额外信息 """ super().__init__(input_type, label, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.text_field = text_field self.value_field = value_field self.provider = provider self.method = method def to_dict(self, **kwargs): return {**super().to_dict(**kwargs), 'text_field': self.text_field, 'value_field': self.value_field, 'provider': self.provider, 'method': self.method} ================================================ FILE: apps/common/forms/base_form.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_form.py @date:2023/11/1 16:04 @desc: """ from typing import Dict from common.forms import BaseField class BaseForm: def to_form_list(self, **kwargs): return [{**self.__getattribute__(key).to_dict(**kwargs), 'field': key} for key in list(filter(lambda key: isinstance(self.__getattribute__(key), BaseField), [attr for attr in vars(self.__class__) if not attr.startswith("__")]))] def valid_form(self, form_data): field_keys = list(filter(lambda key: isinstance(self.__getattribute__(key), BaseField), [attr for attr in vars(self.__class__) if not attr.startswith("__")])) for field_key in field_keys: self.__getattribute__(field_key).is_valid(form_data.get(field_key)) def get_default_form_data(self): return {key: self.__getattribute__(key).default_value for key in [attr for attr in vars(self.__class__) if not attr.startswith("__")] if isinstance(self.__getattribute__(key), BaseField) and self.__getattribute__( key).default_value is not None} ================================================ FILE: apps/common/forms/label/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/8/22 17:19 @desc: """ from .base_label import * from .tooltip_label import * ================================================ FILE: apps/common/forms/label/base_label.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_label.py @date:2024/8/22 17:11 @desc: """ class BaseLabel: def __init__(self, input_type: str, label: str, attrs=None, props_info=None): self.input_type = input_type self.label = label self.attrs = attrs self.props_info = props_info def to_dict(self, **kwargs): return { 'input_type': self.input_type, 'label': self.label, 'attrs': {} if self.attrs is None else self.attrs, 'props_info': {} if self.props_info is None else self.props_info, } ================================================ FILE: apps/common/forms/label/tooltip_label.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: tooltip_label.py @date:2024/8/22 17:19 @desc: """ from common.forms.label.base_label import BaseLabel class TooltipLabel(BaseLabel): def __init__(self, label, tooltip): super().__init__('TooltipLabel', label, attrs={'tooltip': tooltip}, props_info={}) ================================================ FILE: apps/common/forms/multi_select.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: multi_select.py @date:2023/10/31 18:00 @desc: """ from typing import List, Dict from common.forms.base_field import BaseExecField, TriggerType class MultiSelect(BaseExecField): """ 下拉单选 """ def __init__(self, label: str, text_field: str, value_field: str, option_list: List[str:object], provider: str = None, method: str = None, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("MultiSelect", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.option_list = option_list def to_dict(self): return {**super().to_dict(), 'option_list': self.option_list} ================================================ FILE: apps/common/forms/object_card.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: object_card.py @date:2023/10/31 18:02 @desc: """ from typing import Dict from common.forms.base_field import BaseExecField, TriggerType class ObjectCard(BaseExecField): """ 收集对象子表卡片 """ def __init__(self, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("ObjectCard", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) ================================================ FILE: apps/common/forms/password_input.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: password_input.py @date:2023/11/1 14:48 @desc: """ from typing import Dict from common.forms import BaseField, TriggerType class PasswordInputField(BaseField): """ 文本输入框 """ def __init__(self, label: str, required: bool = False, default_value=None, relation_show_field_dict: Dict = None, attrs=None, props_info=None): super().__init__('PasswordInput', label, required, default_value, relation_show_field_dict, {}, TriggerType.OPTION_LIST, attrs, props_info) ================================================ FILE: apps/common/forms/radio_button_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: radio_field.py @date:2023/10/31 17:59 @desc: """ from typing import List, Dict from common.forms.base_field import BaseExecField, TriggerType class RadioButton(BaseExecField): """ 下拉单选 """ def __init__(self, label: str, text_field: str, value_field: str, option_list: List[str:object], provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("RadioButton", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.option_list = option_list def to_dict(self): return {**super().to_dict(), 'option_list': self.option_list} ================================================ FILE: apps/common/forms/radio_card_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: radio_field.py @date:2023/10/31 17:59 @desc: """ from typing import List, Dict from common.forms.base_field import BaseExecField, TriggerType class RadioCard(BaseExecField): """ 下拉单选 """ def __init__(self, label: str, text_field: str, value_field: str, option_list: List[str:object], provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("RadioCard", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.option_list = option_list def to_dict(self): return {**super().to_dict(), 'option_list': self.option_list} ================================================ FILE: apps/common/forms/radio_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: radio_field.py @date:2023/10/31 17:59 @desc: """ from typing import List, Dict from common.forms.base_field import BaseExecField, TriggerType class Radio(BaseExecField): """ 下拉单选 """ def __init__(self, label: str, text_field: str, value_field: str, option_list: List[str:object], provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("Radio", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.option_list = option_list def to_dict(self): return {**super().to_dict(), 'option_list': self.option_list} ================================================ FILE: apps/common/forms/single_select_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: single_select_field.py @date:2023/10/31 18:00 @desc: """ from typing import List, Dict from common.forms import BaseLabel from common.forms.base_field import TriggerType, BaseExecField class SingleSelect(BaseExecField): """ 下拉单选 """ def __init__(self, label: str or BaseLabel, text_field: str, value_field: str, option_list: List[str:object], provider: str = None, method: str = None, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("SingleSelect", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) self.option_list = option_list def to_dict(self): return {**super().to_dict(), 'option_list': self.option_list} ================================================ FILE: apps/common/forms/slider_field.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: slider_field.py @date:2024/8/22 17:06 @desc: """ from typing import Dict from common.exception.app_exception import AppApiException from common.forms import BaseField, TriggerType, BaseLabel from django.utils.translation import gettext_lazy as _ class SliderField(BaseField): """ 滑块输入框 """ def __init__(self, label: str or BaseLabel, _min, _max, _step, precision, required: bool = False, default_value=None, relation_show_field_dict: Dict = None, attrs=None, props_info=None): """ @param label: 提示 @param _min: 最小值 @param _max: 最大值 @param _step: 步长 @param precision: 保留多少小数 @param required: 是否必填 @param default_value: 默认值 @param relation_show_field_dict: @param attrs: @param props_info: """ _attrs = {'min': _min, 'max': _max, 'step': _step, 'precision': precision, 'show-input-controls': False, 'show-input': True} if attrs is not None: _attrs.update(attrs) super().__init__('Slider', label, required, default_value, relation_show_field_dict, {}, TriggerType.OPTION_LIST, _attrs, props_info) def is_valid(self, value): super().is_valid(value) field_label = self.label.label if hasattr(self.label, 'to_dict') else self.label if value is not None: if value < self.attrs.get('min'): raise AppApiException(500, _("The {field_label} cannot be less than {min}").format(field_label=field_label, min=self.attrs.get( 'min'))) if value > self.attrs.get('max'): raise AppApiException(500, _("The {field_label} cannot be greater than {max}").format( field_label=field_label, max=self.attrs.get( 'max'))) ================================================ FILE: apps/common/forms/switch_field.py ================================================ """ @project: MaxKB @Author:虎 @file: switch_field.py @date:2024/10/13 19:43 @desc: """ from typing import Dict from common.forms import BaseField, TriggerType, BaseLabel class SwitchField(BaseField): """ 滑块输入框 """ def __init__(self, label: str or BaseLabel, required: bool = False, default_value=None, relation_show_field_dict: Dict = None, attrs=None, props_info=None): """ @param required: 是否必填 @param default_value: 默认值 @param relation_show_field_dict: @param attrs: @param props_info: """ super().__init__('SwitchInput', label, required, default_value, relation_show_field_dict, {}, TriggerType.OPTION_LIST, attrs, props_info) ================================================ FILE: apps/common/forms/tab_card.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: tab_card.py @date:2023/10/31 18:03 @desc: """ from typing import Dict from common.forms.base_field import BaseExecField, TriggerType class TabCard(BaseExecField): """ 收集 Tab类型数据 tab1:{},tab2:{} """ def __init__(self, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("TabCard", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) ================================================ FILE: apps/common/forms/table_checkbox.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: table_radio.py @date:2023/10/31 18:01 @desc: """ from typing import Dict from common.forms.base_field import TriggerType, BaseExecField class TableRadio(BaseExecField): """ table 单选 """ def __init__(self, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("TableCheckbox", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) ================================================ FILE: apps/common/forms/table_radio.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: table_radio.py @date:2023/10/31 18:01 @desc: """ from typing import Dict from common.forms.base_field import TriggerType, BaseExecField class TableRadio(BaseExecField): """ table 单选 """ def __init__(self, label: str, text_field: str, value_field: str, provider: str, method: str, required: bool = False, default_value: object = None, relation_show_field_dict: Dict = None, relation_trigger_field_dict: Dict = None, trigger_type: TriggerType = TriggerType.OPTION_LIST, attrs: Dict[str, object] = None, props_info: Dict[str, object] = None): super().__init__("TableRadio", label, text_field, value_field, provider, method, required, default_value, relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info) ================================================ FILE: apps/common/forms/text_input_field.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: text_input_field.py @date:2023/10/31 17:58 @desc: """ from typing import Dict from common.forms import BaseLabel from common.forms.base_field import BaseField, TriggerType class TextInputField(BaseField): """ 文本输入框 """ def __init__(self, label: str or BaseLabel, required: bool = False, default_value=None, relation_show_field_dict: Dict = None, attrs=None, props_info=None): super().__init__('TextInput', label, required, default_value, relation_show_field_dict, {}, TriggerType.OPTION_LIST, attrs, props_info) ================================================ FILE: apps/common/handle/__init__.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: __init__.py.py @date:2023/9/6 10:09 @desc: """ ================================================ FILE: apps/common/handle/base_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_parse_qa_handle.py @date:2024/5/21 14:56 @desc: """ from abc import ABC, abstractmethod def get_row_value(row, title_row_index_dict, field): index = title_row_index_dict.get(field) if index is None: return None if (len(row) - 1) >= index: return row[index] return None def get_title_row_index_dict(title_row_list): title_row_index_dict = {} if len(title_row_list) == 1: title_row_index_dict['content'] = 0 elif len(title_row_list) == 1: title_row_index_dict['title'] = 0 title_row_index_dict['content'] = 1 else: title_row_index_dict['title'] = 0 title_row_index_dict['content'] = 1 title_row_index_dict['problem_list'] = 2 for index in range(len(title_row_list)): title_row = title_row_list[index] if title_row is None: title_row = '' if title_row.startswith('分段标题'): title_row_index_dict['title'] = index if title_row.startswith('分段内容'): title_row_index_dict['content'] = index if title_row.startswith('问题'): title_row_index_dict['problem_list'] = index return title_row_index_dict class BaseParseQAHandle(ABC): @abstractmethod def support(self, file, get_buffer): pass @abstractmethod def handle(self, file, get_buffer, save_image): pass ================================================ FILE: apps/common/handle/base_parse_table_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_parse_qa_handle.py @date:2024/5/21 14:56 @desc: """ from abc import ABC, abstractmethod class BaseParseTableHandle(ABC): @abstractmethod def support(self, file, get_buffer): pass @abstractmethod def handle(self, file, get_buffer,save_image): pass @abstractmethod def get_content(self, file, save_image): pass ================================================ FILE: apps/common/handle/base_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_split_handle.py @date:2024/3/27 18:13 @desc: """ from abc import ABC, abstractmethod from typing import List class BaseSplitHandle(ABC): @abstractmethod def support(self, file, get_buffer): pass @abstractmethod def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): pass @abstractmethod def get_content(self, file, save_image): pass ================================================ FILE: apps/common/handle/base_to_response.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_to_response.py @date:2024/9/6 16:04 @desc: """ from abc import ABC, abstractmethod from rest_framework import status class BaseToResponse(ABC): @abstractmethod def to_block_response(self, chat_id, chat_record_id, content, is_end, completion_tokens, prompt_tokens, other_params: dict = None, _status=status.HTTP_200_OK): pass @abstractmethod def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens, prompt_tokens, other_params: dict = None): pass @staticmethod def format_stream_chunk(response_str): return 'data: ' + response_str + '\n\n' ================================================ FILE: apps/common/handle/handle_exception.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: handle_exception.py @date:2023/9/5 19:29 @desc: """ import logging import traceback from rest_framework.exceptions import ValidationError, ErrorDetail, APIException from rest_framework.views import exception_handler from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ from common.result import result from common.utils.logger import maxkb_logger def to_result(key, args, parent_key=None): """ 将校验异常 args转换为统一数据 :param key: 校验key :param args: 校验异常参数 :param parent_key 父key :return: 接口响应对象 """ error_detail = list(filter( lambda d: True if isinstance(d, ErrorDetail) else True if isinstance(d, dict) and len( d.keys()) > 0 else False, (args[0] if len(args) > 0 else {key: [ErrorDetail(_('Unknown exception'), code='unknown')]}).get(key)))[0] if isinstance(error_detail, dict): return list(map(lambda k: to_result(k, args=[error_detail], parent_key=key if parent_key is None else parent_key + '.' + key), error_detail.keys() if len(error_detail) > 0 else []))[0] return result.Result(500 if isinstance(error_detail.code, str) else error_detail.code, message=f"【{key if parent_key is None else parent_key + '.' + key}】为必填参数" if str( error_detail) == "This field is required." else error_detail) def validation_error_to_result(exc: ValidationError): """ 校验异常转响应对象 :param exc: 校验异常 :return: 接口响应对象 """ try: v = find_err_detail(exc.detail) if v is None: return result.error(str(exc.detail)) return result.error(str(v)) except Exception as e: return result.error(str(exc.detail)) def find_err_detail(exc_detail): if isinstance(exc_detail, ErrorDetail): return exc_detail if isinstance(exc_detail, dict): keys = exc_detail.keys() for key in keys: _value = exc_detail[key] if isinstance(_value, list): return find_err_detail(_value) if isinstance(_value, ErrorDetail): return _value if isinstance(_value, dict) and len(_value.keys()) > 0: return find_err_detail(_value) if isinstance(exc_detail, list): for v in exc_detail: r = find_err_detail(v) if r is not None: return r def handle_exception(exc, context): exception_class = exc.__class__ # 先调用REST framework默认的异常处理方法获得标准错误响应对象 response = exception_handler(exc, context) # 在此处补充自定义的异常处理 if issubclass(exception_class, ValidationError): return validation_error_to_result(exc) if issubclass(exception_class, AppApiException): return result.Result(exc.code, exc.message, response_status=exc.status_code) if issubclass(exception_class, APIException): return result.error(exc.detail) if response is None: maxkb_logger.error(f'{str(exc)}:{traceback.format_exc()}') return result.error(str(exc)) return response ================================================ FILE: apps/common/handle/impl/__init__.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: __init__.py.py @date:2023/9/6 10:09 @desc: """ ================================================ FILE: apps/common/handle/impl/common_handle.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: tools.py @date:2024/9/11 16:41 @desc: """ import io import traceback from functools import reduce from io import BytesIO from xml.etree.ElementTree import fromstring from zipfile import ZipFile import uuid_utils.compat as uuid from PIL import Image as PILImage from openpyxl.drawing.image import Image as openpyxl_Image from openpyxl.packaging.relationship import get_rels_path, get_dependents from openpyxl.xml.constants import SHEET_DRAWING_NS, REL_NS, SHEET_MAIN_NS from common.utils.logger import maxkb_logger from knowledge.models import File from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES = True PILImage.MAX_IMAGE_PIXELS = None def parse_element(element) -> {}: data = {} xdr_namespace = "{%s}" % SHEET_DRAWING_NS targets = level_order_traversal(element, xdr_namespace + "nvPicPr") for target in targets: cNvPr = embed = "" for child in target: if child.tag == xdr_namespace + "nvPicPr": cNvPr = child[0].attrib["name"] elif child.tag == xdr_namespace + "blipFill": _rel_embed = "{%s}embed" % REL_NS embed = child[0].attrib[_rel_embed] if cNvPr: data[cNvPr] = embed return data def parse_element_sheet_xml(element) -> []: data = [] xdr_namespace = "{%s}" % SHEET_MAIN_NS targets = level_order_traversal(element, xdr_namespace + "f") for target in targets: for child in target: if child.tag == xdr_namespace + "f": data.append(child.text) return data def level_order_traversal(root, flag: str) -> []: queue = [root] targets = [] while queue: node = queue.pop(0) children = [child.tag for child in node] if flag in children: targets.append(node) continue for child in node: queue.append(child) return targets def handle_images(deps, archive: ZipFile) -> []: images = [] if not PILImage: # Pillow not installed, drop images return images for dep in deps: try: image_io = archive.read(dep.target) image = openpyxl_Image(BytesIO(image_io)) except Exception as e: maxkb_logger.error(f"Error reading image {dep.target}: {e}, {traceback.format_exc()}") continue image.embed = dep.id # 文件rId image.target = dep.target # 文件地址 images.append(image) return images def xlsx_embed_cells_images(buffer) -> {}: archive = ZipFile(buffer) # 解析cellImage.xml文件 deps = get_dependents(archive, get_rels_path("xl/cellimages.xml")) image_rel = handle_images(deps=deps, archive=archive) # 工作表及其中图片ID sheet_list = {} for item in archive.namelist(): if not item.startswith('xl/worksheets/sheet'): continue key = item.split('/')[-1].split('.')[0].split('sheet')[-1] sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item))) cell_images_xml = parse_element(fromstring(archive.read("xl/cellimages.xml"))) cell_images_rel = {} for image in image_rel: cell_images_rel[image.embed] = image for cnv, embed in cell_images_xml.items(): cell_images_xml[cnv] = cell_images_rel.get(embed) result = {} for key, img in cell_images_xml.items(): all_cells = [ cell for _sheet_id, sheet in sheet_list.items() if sheet is not None for cell in sheet or [] ] image_excel_id_list = [ cell for cell in all_cells if isinstance(cell, str) and key in cell ] # print(key, img) if img is None: continue if len(image_excel_id_list) > 0: image_excel_id = image_excel_id_list[-1] f = archive.open(img.target) img_byte = io.BytesIO() im = PILImage.open(f).convert('RGB') im.save(img_byte, format='JPEG') image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()}) result['=' + image_excel_id] = image archive.close() return result ================================================ FILE: apps/common/handle/impl/qa/__init__.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: __init__.py.py @date:2023/9/6 10:09 @desc: """ ================================================ FILE: apps/common/handle/impl/qa/csv_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: csv_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import csv import io import traceback from charset_normalizer import detect from common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value from common.utils.logger import maxkb_logger def read_csv_standard(file_path): data = [] with open(file_path, 'r') as file: reader = csv.reader(file) for row in reader: data.append(row) return data class CsvParseQAHandle(BaseParseQAHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".csv"): return True return False def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding'])) try: title_row_list = reader.__next__() except Exception as e: return [{'name': file.name, 'paragraphs': []}] if len(title_row_list) == 0: return [{'name': file.name, 'paragraphs': []}] title_row_index_dict = get_title_row_index_dict(title_row_list) paragraph_list = [] for row in reader: content = get_row_value(row, title_row_index_dict, 'content') if content is None: continue problem = get_row_value(row, title_row_index_dict, 'problem_list') problem = str(problem) if problem is not None else '' problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0] title = get_row_value(row, title_row_index_dict, 'title') title = str(title) if title is not None else '' paragraph_list.append({'title': title[0:255], 'content': content[0:102400], 'problem_list': problem_list}) return [{'name': file.name, 'paragraphs': paragraph_list}] except Exception as e: maxkb_logger.error(f"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] ================================================ FILE: apps/common/handle/impl/qa/md_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: md_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import re import traceback from typing import Any from charset_normalizer import detect from common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value from common.utils.logger import maxkb_logger class MarkdownParseQAHandle(BaseParseQAHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".md") or file_name.endswith(".markdown"): return True return False def parse_markdown_table(self, content): """解析 Markdown 表格,返回表格数据列表""" tables = [] lines = content.split('\n') i = 0 while i < len(lines): line = lines[i].strip() # 检测表格开始(包含 | 符号) if '|' in line and line.startswith('|'): table_data = [] # 读取表头 header = [cell.strip() for cell in line.split('|')[1:-1]] table_data.append(header) i += 1 # 跳过分隔行 (例如: | --- | --- |) if i < len(lines) and re.match(r'\s*\|[\s\-:]+\|\s*', lines[i]): i += 1 # 读取数据行 while i < len(lines): line = lines[i].strip() if not line.startswith('|'): break row = [self._unescape_cell_content(cell) for cell in line.split('|')[1:-1]] if len(row) > 0: table_data.append(row) i += 1 if len(table_data) > 1: # 至少有表头和一行数据 tables.append(table_data) else: i += 1 return tables def _unescape_cell_content(self, cell) -> Any: text = cell.strip().replace('|', '|') text = text.replace('|
|', '|\n|') return text def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: # 检测编码并读取文件内容 encoding = detect(buffer)['encoding'] content = buffer.decode(encoding if encoding else 'utf-8') # 按 sheet 分割内容 sheet_sections = self.split_by_sheets(content) result = [] for sheet_name, sheet_content in sheet_sections: # 解析该 sheet 的表格 tables = self.parse_markdown_table(sheet_content) paragraph_list = [] # 处理每个表格 for table in tables: if len(table) < 2: continue title_row_list = table[0] title_row_index_dict = get_title_row_index_dict(title_row_list) # 处理表格的每一行数据 for row in table[1:]: content_text = get_row_value(row, title_row_index_dict, 'content') if content_text is None: continue problem = get_row_value(row, title_row_index_dict, 'problem_list') problem = str(problem) if problem is not None else '' problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0] title = get_row_value(row, title_row_index_dict, 'title') title = str(title) if title is not None else '' paragraph_list.append({ 'title': title[0:255], 'content': content_text[0:102400], 'problem_list': problem_list }) result.append({'name': sheet_name, 'paragraphs': paragraph_list}) return result if result else [{'name': file.name, 'paragraphs': []}] except Exception as e: maxkb_logger.error(f"Error processing Markdown file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] def split_by_sheets(self, content): """按二级标题(##)分割 sheet""" lines = content.split('\n') sheets = [] current_sheet_name = None current_content = [] for line in lines: # 检测二级标题作为 sheet 名称 if line.strip().startswith('## '): if current_sheet_name is not None: sheets.append((current_sheet_name, '\n'.join(current_content))) current_sheet_name = line.strip()[3:].strip() current_content = [] else: current_content.append(line) # 添加最后一个 sheet if current_sheet_name is not None: sheets.append((current_sheet_name, '\n'.join(current_content))) # 如果没有找到 sheet 标题,返回整个内容 if not sheets: sheets.append(('default', content)) return sheets ================================================ FILE: apps/common/handle/impl/qa/xls_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: xls_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import traceback import xlrd from common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value from common.utils.logger import maxkb_logger def handle_sheet(file_name, sheet): rows = iter([sheet.row_values(i) for i in range(sheet.nrows)]) try: title_row_list = next(rows) except Exception as e: return {'name': file_name, 'paragraphs': []} if len(title_row_list) == 0: return {'name': file_name, 'paragraphs': []} title_row_index_dict = get_title_row_index_dict(title_row_list) paragraph_list = [] for row in rows: content = get_row_value(row, title_row_index_dict, 'content') if content is None: continue problem = get_row_value(row, title_row_index_dict, 'problem_list') problem = str(problem) if problem is not None else '' problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0] title = get_row_value(row, title_row_index_dict, 'title') title = str(title) if title is not None else '' content = str(content) paragraph_list.append({'title': title[0:255], 'content': content[0:102400], 'problem_list': problem_list}) return {'name': file_name, 'paragraphs': paragraph_list} class XlsParseQAHandle(BaseParseQAHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() buffer = get_buffer(file) if file_name.endswith(".xls") and xlrd.inspect_format(content=buffer): return True return False def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: workbook = xlrd.open_workbook(file_contents=buffer) worksheets = workbook.sheets() worksheets_size = len(worksheets) return [row for row in [handle_sheet(file.name, sheet) if worksheets_size == 1 and sheet.name == 'Sheet1' else handle_sheet( sheet.name, sheet) for sheet in worksheets] if row is not None] except Exception as e: maxkb_logger.error(f"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] ================================================ FILE: apps/common/handle/impl/qa/xlsx_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: xlsx_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import io import traceback import openpyxl from common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value from common.handle.impl.common_handle import xlsx_embed_cells_images from common.utils.logger import maxkb_logger def handle_sheet(file_name, sheet, image_dict): rows = sheet.rows try: title_row_list = next(rows) title_row_list = [row.value for row in title_row_list] except Exception as e: return {'name': file_name, 'paragraphs': []} if len(title_row_list) == 0: return {'name': file_name, 'paragraphs': []} title_row_index_dict = get_title_row_index_dict(title_row_list) paragraph_list = [] for row in rows: content = get_row_value(row, title_row_index_dict, 'content') if content is None or content.value is None: continue problem = get_row_value(row, title_row_index_dict, 'problem_list') problem = str(problem.value) if problem is not None and problem.value is not None else '' problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0] title = get_row_value(row, title_row_index_dict, 'title') title = str(title.value) if title is not None and title.value is not None else '' content = str(content.value) image = image_dict.get(content, None) if image is not None: content = f'![](./oss/file/{image.id})' paragraph_list.append({'title': title[0:255], 'content': content[0:102400], 'problem_list': problem_list}) return {'name': file_name, 'paragraphs': paragraph_list} class XlsxParseQAHandle(BaseParseQAHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".xlsx"): return True return False def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: workbook = openpyxl.load_workbook(io.BytesIO(buffer)) try: image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer)) save_image([item for item in image_dict.values()]) except Exception as e: image_dict = {} worksheets = workbook.worksheets worksheets_size = len(worksheets) return [row for row in [handle_sheet(file.name, sheet, image_dict) if worksheets_size == 1 and sheet.title == 'Sheet1' else handle_sheet( sheet.title, sheet, image_dict) for sheet in worksheets] if row is not None] except Exception as e: maxkb_logger.error(f"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] ================================================ FILE: apps/common/handle/impl/qa/zip_parse_qa_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: text_split_handle.py @date:2024/3/27 18:19 @desc: """ import io import os import re import zipfile from typing import List from urllib.parse import urljoin import uuid_utils.compat as uuid from django.utils.translation import gettext_lazy as _ from common.handle.base_parse_qa_handle import BaseParseQAHandle from common.handle.impl.qa.csv_parse_qa_handle import CsvParseQAHandle from common.handle.impl.qa.xls_parse_qa_handle import XlsParseQAHandle from common.handle.impl.qa.xlsx_parse_qa_handle import XlsxParseQAHandle from common.utils.common import parse_md_image from knowledge.models import File class FileBufferHandle: buffer = None def get_buffer(self, file): if self.buffer is None: self.buffer = file.read() return self.buffer split_handles = [ XlsParseQAHandle(), XlsxParseQAHandle(), CsvParseQAHandle() ] def file_to_paragraph(file, save_inner_image): """ 文件转换为段落列表 @param file: 文件 @return: { name:文件名 paragraphs:段落列表 } """ get_buffer = FileBufferHandle().get_buffer for split_handle in split_handles: if split_handle.support(file, get_buffer): return split_handle.handle(file, get_buffer, save_inner_image) raise Exception(_("Unsupported file format")) def is_valid_uuid(uuid_str: str): """ 校验字符串是否是uuid @param uuid_str: 需要校验的字符串 @return: bool """ try: uuid.UUID(uuid_str) except ValueError: return False return True def get_image_list(result_list: list, zip_files: List[str]): """ 获取图片文件列表 @param result_list: @param zip_files: @return: """ image_file_list = [] for result in result_list: for p in result.get('paragraphs', []): content: str = p.get('content', '') image_list = parse_md_image(content) for image in image_list: search = re.search("\(.*\)", image) if search: new_image_id = str(uuid.uuid7()) source_image_path = search.group().replace('(', '').replace(')', '') image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith( '/') else source_image_path) if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): image_id = image_path.replace('oss/file/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') p['content'] = content else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') p['content'] = content return image_file_list def filter_image_file(result_list: list, image_list): image_source_file_list = [image.get('source_file') for image in image_list] return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))] class ZipParseQAHandle(BaseParseQAHandle): def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) bytes_io = io.BytesIO(buffer) result = [] # 打开zip文件 with zipfile.ZipFile(bytes_io, 'r') as zip_ref: # 获取压缩包中的文件名列表 files = zip_ref.namelist() # 读取压缩包中的文件内容 for file in files: # 跳过 macOS 特有的元数据目录和文件 if file.endswith('/') or file.startswith('__MACOSX'): continue with zip_ref.open(file) as f: # 对文件内容进行处理 try: value = file_to_paragraph(f, save_image) if isinstance(value, list): result = [*result, *value] else: result.append(value) except Exception: pass image_list = get_image_list(result, files) result = filter_image_file(result, image_list) image_mode_list = [] for image in image_list: with zip_ref.open(image.get('source_file')) as f: i = File( id=image.get('image_id'), file_name=os.path.basename(image.get('source_file')), meta={'debug': False, 'content': f.read()} ) image_mode_list.append(i) save_image(image_mode_list) return result def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".zip") or file_name.endswith(".ZIP"): return True return False ================================================ FILE: apps/common/handle/impl/response/__init__.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: __init__.py.py @date:2023/9/6 10:09 @desc: """ ================================================ FILE: apps/common/handle/impl/response/loop_to_response.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: LoopToResponse.py @date:2025/3/12 17:21 @desc: """ import json from common.handle.impl.response.system_to_response import SystemToResponse class LoopToResponse(SystemToResponse): def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens, prompt_tokens, other_params: dict = None): if other_params is None: other_params = {} return {'chat_id': str(chat_id), 'chat_record_id': str(chat_record_id), 'operate': True, 'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list, 'is_end': is_end, 'usage': {'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens, 'total_tokens': completion_tokens + prompt_tokens}, **other_params} ================================================ FILE: apps/common/handle/impl/response/openai_to_response.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: openai_to_response.py @date:2024/9/6 16:08 @desc: """ import datetime from django.http import JsonResponse from django.utils import timezone from openai.types import CompletionUsage from openai.types.chat import ChatCompletionChunk, ChatCompletionMessage, ChatCompletion from openai.types.chat.chat_completion import Choice as BlockChoice from openai.types.chat.chat_completion_chunk import Choice, ChoiceDelta from rest_framework import status from common.handle.base_to_response import BaseToResponse class OpenaiToResponse(BaseToResponse): def to_block_response(self, chat_id, chat_record_id, content, is_end, prompt_tokens, completion_tokens, other_params: dict = None, _status=status.HTTP_200_OK): if other_params is None: other_params = {} data = ChatCompletion(id=chat_record_id, choices=[ BlockChoice(finish_reason='stop', index=0, chat_id=chat_id, answer_list=other_params.get('answer_list', ""), message=ChatCompletionMessage(role='assistant', content=content))], created=timezone.now().second, model='', object='chat.completion', usage=CompletionUsage(completion_tokens=completion_tokens, prompt_tokens=prompt_tokens, total_tokens=completion_tokens + prompt_tokens) ).dict() return JsonResponse(data=data, status=_status) def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, prompt_tokens, completion_tokens, other_params: dict = None): if other_params is None: other_params = {} chunk = ChatCompletionChunk(id=chat_record_id, model='', object='chat.completion.chunk', created=timezone.now().second, choices=[ Choice(delta=ChoiceDelta(content=content, reasoning_content=other_params.get('reasoning_content', ""), chat_id=chat_id), finish_reason='stop' if is_end else None, index=0)], usage=CompletionUsage(completion_tokens=completion_tokens, prompt_tokens=prompt_tokens, total_tokens=completion_tokens + prompt_tokens)).json() return super().format_stream_chunk(chunk) ================================================ FILE: apps/common/handle/impl/response/system_to_response.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: system_to_response.py @date:2024/9/6 18:03 @desc: """ import json from rest_framework import status from common.handle.base_to_response import BaseToResponse from common.result import result class SystemToResponse(BaseToResponse): def to_block_response(self, chat_id, chat_record_id, content, is_end, completion_tokens, prompt_tokens, other_params: dict = None, _status=status.HTTP_200_OK): if other_params is None: other_params = {} return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': content, 'is_end': is_end, **other_params, 'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens}, response_status=_status, code=_status) def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens, prompt_tokens, other_params: dict = None): if other_params is None: other_params = {} chunk = json.dumps({'chat_id': str(chat_id), 'chat_record_id': str(chat_record_id), 'operate': True, 'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list, 'is_end': is_end, 'usage': {'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens, 'total_tokens': completion_tokens + prompt_tokens}, **other_params}) return super().format_stream_chunk(chunk) ================================================ FILE: apps/common/handle/impl/table/__init__.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: __init__.py.py @date:2023/9/6 10:09 @desc: """ ================================================ FILE: apps/common/handle/impl/table/csv_parse_table_handle.py ================================================ # coding=utf-8 import csv import io import traceback from charset_normalizer import detect from common.handle.base_parse_qa_handle import get_title_row_index_dict, get_row_value from common.handle.base_parse_table_handle import BaseParseTableHandle from common.utils.logger import maxkb_logger class CsvParseTableHandle(BaseParseTableHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".csv"): return True return False def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: content = buffer.decode(detect(buffer)['encoding']) except BaseException as e: maxkb_logger.error(f"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] csv_model = content.split('\n') paragraphs = [] # 第一行为标题 title = csv_model[0].split(',') for row in csv_model[1:]: if not row: continue line = '; '.join([f'{key}:{value}' for key, value in zip(title, row.split(','))]) paragraphs.append({'title': '', 'content': line}) return [{'name': file.name, 'paragraphs': paragraphs}] def get_content(self, file, save_image): buffer = file.read() try: reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding'])) rows = list(reader) if not rows: return "" # 构建 Markdown 表格 md_lines = [] # 添加表头 header = [cell.replace('\n', '
').replace('\r', '') for cell in rows[0]] md_lines.append('| ' + ' | '.join(header) + ' |') # 添加分隔线 md_lines.append('| ' + ' | '.join(['---'] * len(header)) + ' |') # 添加数据行 for row in rows[1:]: if row: # 跳过空行 # 确保行长度与表头一致,并将换行符转换为
padded_row = [ cell.replace('\n', '
').replace('\r', '') for cell in row ] + [''] * (len(header) - len(row)) md_lines.append('| ' + ' | '.join(padded_row[:len(header)]) + ' |') return '\n'.join(md_lines) except Exception as e: maxkb_logger.error(f"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}") return "" ================================================ FILE: apps/common/handle/impl/table/xls_parse_table_handle.py ================================================ # coding=utf-8 import logging import traceback import xlrd from common.handle.base_parse_table_handle import BaseParseTableHandle from common.utils.logger import maxkb_logger class XlsParseTableHandle(BaseParseTableHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() buffer = get_buffer(file) if file_name.endswith(".xls") and xlrd.inspect_format(content=buffer): return True return False def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: wb = xlrd.open_workbook(file_contents=buffer, formatting_info=True) result = [] sheets = wb.sheets() for sheet in sheets: # 获取合并单元格的范围信息 merged_cells = sheet.merged_cells data = [] paragraphs = [] # 获取第一行作为标题行 headers = [sheet.cell_value(0, col_idx) for col_idx in range(sheet.ncols)] # 从第二行开始遍历每一行(跳过标题行) for row_idx in range(1, sheet.nrows): row_data = {} for col_idx in range(sheet.ncols): cell_value = sheet.cell_value(row_idx, col_idx) # 检查是否为空单元格,如果为空检查是否在合并区域中 if cell_value == "": # 检查当前单元格是否在合并区域 for (rlo, rhi, clo, chi) in merged_cells: if rlo <= row_idx < rhi and clo <= col_idx < chi: # 使用合并区域的左上角单元格的值 cell_value = sheet.cell_value(rlo, clo) break # 将标题作为键,单元格的值作为值存入字典 row_data[headers[col_idx]] = cell_value data.append(row_data) for row in data: row_output = "; ".join([f"{key}: {value}" for key, value in row.items()]) # print(row_output) paragraphs.append({'title': '', 'content': row_output}) result.append({'name': sheet.name, 'paragraphs': paragraphs}) except BaseException as e: maxkb_logger.error(f"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] return result def get_content(self, file, save_image): # 打开 .xls 文件 try: workbook = xlrd.open_workbook(file_contents=file.read(), formatting_info=True) sheets = workbook.sheets() md_tables = '' for sheet in sheets: # 过滤空白的sheet if sheet.nrows == 0 or sheet.ncols == 0: continue # 获取表头和内容 headers = sheet.row_values(0) data = [sheet.row_values(row_idx) for row_idx in range(1, sheet.nrows)] # 构建 Markdown 表格 md_table = '| ' + ' | '.join(headers) + ' |\n' md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\n' for row in data: # 将每个单元格中的内容替换换行符为
以保留原始格式 md_table += '| ' + ' | '.join( [str(cell) .replace('\r\n', '
') .replace('\n', '
') if cell else '' for cell in row]) + ' |\n' md_tables += md_table + '\n\n' return md_tables except Exception as e: maxkb_logger.error(f'excel split handle error: {e}') return f'error: {e}' ================================================ FILE: apps/common/handle/impl/table/xlsx_parse_table_handle.py ================================================ # coding=utf-8 import io import logging import traceback from openpyxl import load_workbook from common.handle.base_parse_table_handle import BaseParseTableHandle from common.handle.impl.common_handle import xlsx_embed_cells_images from common.utils.logger import maxkb_logger class XlsxParseTableHandle(BaseParseTableHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith('.xlsx'): return True return False def fill_merged_cells(self, sheet, image_dict): data = [] # 获取第一行作为标题行 headers = [] for idx, cell in enumerate(sheet[1]): if cell.value is None: headers.append(' ' * (idx + 1)) else: headers.append(cell.value) # 从第二行开始遍历每一行 for row in sheet.iter_rows(min_row=2, values_only=False): row_data = {} for col_idx, cell in enumerate(row): cell_value = cell.value # 如果单元格为空,并且该单元格在合并单元格内,获取合并单元格的值 if cell_value is None: for merged_range in sheet.merged_cells.ranges: if cell.coordinate in merged_range: cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value break if cell_value is None: cell_value = '' image = image_dict.get(cell_value, None) if image is not None: cell_value = f'![](./oss/file/{image.id})' # 使用标题作为键,单元格的值作为值存入字典 row_data[headers[col_idx]] = cell_value data.append(row_data) return data def handle(self, file, get_buffer, save_image): buffer = get_buffer(file) try: wb = load_workbook(io.BytesIO(buffer)) try: image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer)) save_image([item for item in image_dict.values()]) except Exception as e: image_dict = {} result = [] for sheetname in wb.sheetnames: paragraphs = [] ws = wb[sheetname] data = self.fill_merged_cells(ws, image_dict) for row in data: row_output = "; ".join([f"{key}: {value}" for key, value in row.items()]) # print(row_output) paragraphs.append({'title': '', 'content': row_output}) result.append({'name': sheetname, 'paragraphs': paragraphs}) except BaseException as e: maxkb_logger.error(f"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'paragraphs': []}] return result def get_content(self, file, save_image): try: # 加载 Excel 文件 workbook = load_workbook(file) try: image_dict: dict = xlsx_embed_cells_images(file) if len(image_dict) > 0: save_image(image_dict.values()) except Exception as e: maxkb_logger.error(f'Exception: {e}') image_dict = {} md_tables = '' # 遍历所有工作表 for sheetname in workbook.sheetnames: sheet = workbook[sheetname] rows = self.fill_merged_cells(sheet, image_dict) if len(rows) == 0: continue # 添加 sheet 名称作为标题 md_tables += f'## {sheetname}\n\n' # 提取表头和内容 headers = [f"{key}" for key, value in rows[0].items()] # 构建 Markdown 表格 md_table = '| ' + ' | '.join(headers) + ' |\n' md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\n' for row in rows: r = [f'{value}' for key, value in row.items()] md_table += '| ' + ' | '.join( [str(cell).replace('\n', '
') if cell is not None else '' for cell in r]) + ' |\n' md_tables += md_table + '\n\n' return md_tables except Exception as e: maxkb_logger.error(f'excel split handle error: {e}') return f'error: {e}' ================================================ FILE: apps/common/handle/impl/text/__init__.py ================================================ ================================================ FILE: apps/common/handle/impl/text/csv_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: csv_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import csv import io import os import traceback from typing import List from charset_normalizer import detect from common.handle.base_split_handle import BaseSplitHandle from common.utils.logger import maxkb_logger def post_cell(cell_value): return cell_value.replace('\n', '
').replace('|', '|') def row_to_md(row): return '| ' + ' | '.join( [post_cell(cell) if cell is not None else '' for cell in row]) + ' |\n' class CsvSplitHandle(BaseSplitHandle): def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): buffer = get_buffer(file) paragraphs = [] file_name = os.path.basename(file.name) result = {'name': file_name, 'content': paragraphs} try: if type(limit) is str: limit = int(limit) reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding'])) try: title_row_list = reader.__next__() title_md_content = row_to_md(title_row_list) title_md_content += '| ' + ' | '.join( ['---' if cell is not None else '' for cell in title_row_list]) + ' |\n' except Exception as e: return result if len(title_row_list) == 0: return result result_item_content = '' for row in reader: next_md_content = row_to_md(row) next_md_content_len = len(next_md_content) result_item_content_len = len(result_item_content) if len(result_item_content) == 0: result_item_content += title_md_content result_item_content += next_md_content else: if result_item_content_len + next_md_content_len < limit: result_item_content += next_md_content else: paragraphs.append({'content': result_item_content, 'title': ''}) result_item_content = title_md_content + next_md_content if len(result_item_content) > 0: paragraphs.append({'content': result_item_content, 'title': ''}) return result except Exception as e: maxkb_logger.error(f"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}") return result def get_content(self, file, save_image): buffer = file.read() try: reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding'])) rows = list(reader) if not rows: return "" # 构建 Markdown 表格 md_lines = [] # 添加表头 header = [cell.replace('\n', '
').replace('\r', '') for cell in rows[0]] md_lines.append('| ' + ' | '.join(header) + ' |') # 添加分隔线 md_lines.append('| ' + ' | '.join(['---'] * len(header)) + ' |') # 添加数据行 for row in rows[1:]: if row: # 跳过空行 # 确保行长度与表头一致,并将换行符转换为
padded_row = [ cell.replace('\n', '
').replace('\r', '') for cell in row ] + [''] * (len(header) - len(row)) md_lines.append('| ' + ' | '.join(padded_row[:len(header)]) + ' |') return '\n'.join(md_lines) except Exception as e: maxkb_logger.error(f"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}") return "" def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".csv"): return True return False ================================================ FILE: apps/common/handle/impl/text/doc_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: text_split_handle.py @date:2024/3/27 18:19 @desc: """ import io import os import re import traceback from functools import reduce from typing import List import uuid_utils.compat as uuid from docx import Document, ImagePart from docx.oxml import ns from docx.table import Table from docx.text.paragraph import Paragraph from common.handle.base_split_handle import BaseSplitHandle from common.utils.logger import maxkb_logger from common.utils.split_model import SplitModel from knowledge.models import File default_pattern_list = [re.compile('(?<=^)# .*|(?<=\\n)# .*'), re.compile('(?<=\\n)(? 0: for image in _images: images.append({'image': image, 'get_image_id_handle': get_image_id_handle}) except Exception as e: pass return images def images_to_string(images, doc: Document, images_list, get_image_id): return "".join( [item for item in [image_to_mode(image, doc, images_list, get_image_id) for image in images] if item is not None]) def get_paragraph_element_txt(paragraph_element, doc: Document, images_list, get_image_id): try: images = get_paragraph_element_images(paragraph_element, doc, images_list, get_image_id) if len(images) > 0: return images_to_string(images, doc, images_list, get_image_id) elif paragraph_element.text is not None: return paragraph_element.text return "" except Exception as e: maxkb_logger.error(f'Error getting paragraph element text: {e}') return "" def get_paragraph_txt(paragraph: Paragraph, doc: Document, images_list, get_image_id): try: return "".join([get_paragraph_element_txt(e, doc, images_list, get_image_id) for e in paragraph._element]) except Exception as e: return "" def get_cell_text(cell, doc: Document, images_list, get_image_id): try: return "".join( [get_paragraph_txt(paragraph, doc, images_list, get_image_id) for paragraph in cell.paragraphs]).replace( "\n", '
') except Exception as e: return "" def get_image_id_func(): image_map = {} def get_image_id(image_id): _v = image_map.get(image_id) if _v is None: image_map[image_id] = uuid.uuid7() return image_map.get(image_id) return _v return get_image_id title_font_list = [ [36, 100], [26, 36], [24, 26], [22, 24], [18, 22], [16, 18] ] def get_title_level(paragraph: Paragraph): try: if paragraph.style is not None: psn = paragraph.style.name if psn.startswith('Heading') or psn.startswith('TOC 标题') or psn.startswith('标题'): return int(psn.replace("Heading ", '').replace('TOC 标题', '').replace('标题', '')) if len(paragraph.runs) >= 1: font_size = paragraph.runs[0].font.size pt = font_size.pt if pt >= 16: for _value, index in zip(title_font_list, range(len(title_font_list))): if pt >= _value[0] and pt < _value[1] and any([run.font.bold for run in paragraph.runs]): return index + 1 except Exception as e: pass return None class DocSplitHandle(BaseSplitHandle): @staticmethod def paragraph_to_md(paragraph: Paragraph, doc: Document, images_list, get_image_id): try: title_level = get_title_level(paragraph) if title_level is not None: title = "".join(["#" for i in range(title_level)]) + " " + paragraph.text images = reduce(lambda x, y: [*x, *y], [get_paragraph_element_images(e, doc, images_list, get_image_id) for e in paragraph._element], []) if len(images) > 0: return title + '\n' + images_to_string(images, doc, images_list, get_image_id) if len( paragraph.text) > 0 else images_to_string(images, doc, images_list, get_image_id) return title except Exception as e: maxkb_logger.error(f"Error processing DOC file: {e}, {traceback.format_exc()}") return paragraph.text return get_paragraph_txt(paragraph, doc, images_list, get_image_id) @staticmethod def table_to_md(table, doc: Document, images_list, get_image_id): rows = table.rows # 创建 Markdown 格式的表格 md_table = '| ' + ' | '.join( [get_cell_text(cell, doc, images_list, get_image_id) for cell in rows[0].cells]) + ' |\n' md_table += '| ' + ' | '.join(['---' for i in range(len(rows[0].cells))]) + ' |\n' for row in rows[1:]: md_table += '| ' + ' | '.join( [get_cell_text(cell, doc, images_list, get_image_id) for cell in row.cells]) + ' |\n' return md_table def to_md(self, doc, images_list, get_image_id): elements = [] for element in doc.element.body: tag = str(element.tag) if tag.endswith('tbl'): # 处理表格 table = Table(element, doc) elements.append(table) elif tag.endswith('p'): # 处理段落 paragraph = Paragraph(element, doc) elements.append(paragraph) return "\n".join( [self.paragraph_to_md(element, doc, images_list, get_image_id) if isinstance(element, Paragraph) else self.table_to_md( element, doc, images_list, get_image_id) for element in elements]) def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): file_name = os.path.basename(file.name) try: if type(limit) is str: limit = int(limit) if type(with_filter) is str: with_filter = with_filter.lower() == 'true' image_list = [] buffer = get_buffer(file) doc = Document(io.BytesIO(buffer)) content = self.to_md(doc, image_list, get_image_id_func()) if len(image_list) > 0: save_image(image_list) if pattern_list is not None and len(pattern_list) > 0: split_model = SplitModel(pattern_list, with_filter, limit) else: split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit) except BaseException as e: maxkb_logger.error(f"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}") return { 'name': file_name, 'content': [] } return { 'name': file_name, 'content': split_model.parse(content) } def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".docx") or file_name.endswith(".doc") or file_name.endswith( ".DOC") or file_name.endswith(".DOCX"): return True return False def get_content(self, file, save_image): try: image_list = [] buffer = file.read() doc = Document(io.BytesIO(buffer)) content = self.to_md(doc, image_list, get_image_id_func()) if len(image_list) > 0: save_image(image_list) return content except BaseException as e: traceback.print_exception(e) return f'{e}' ================================================ FILE: apps/common/handle/impl/text/html_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: html_split_handle.py @date:2024/5/23 10:58 @desc: """ import re import traceback from typing import List from bs4 import BeautifulSoup from charset_normalizer import detect from html2text import html2text from common.handle.base_split_handle import BaseSplitHandle from common.utils.logger import maxkb_logger from common.utils.split_model import SplitModel default_pattern_list = [re.compile('(?<=^)# .*|(?<=\\n)# .*'), re.compile('(?<=\\n)(? 0: charset = charset_list[0] return charset return detect(buffer)['encoding'] class HTMLSplitHandle(BaseSplitHandle): def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".html") or file_name.endswith(".HTML"): return True return False def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): buffer = get_buffer(file) if type(limit) is str: limit = int(limit) if type(with_filter) is str: with_filter = with_filter.lower() == 'true' if pattern_list is not None and len(pattern_list) > 0: split_model = SplitModel(pattern_list, with_filter, limit) else: split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit) try: encoding = get_encoding(buffer) content = buffer.decode(encoding) content = html2text(content) except BaseException as e: maxkb_logger.error(f"Error processing HTML file {file.name}: {e}, {traceback.format_exc()}") return { 'name': file.name, 'content': [] } return { 'name': file.name, 'content': split_model.parse(content) } def get_content(self, file, save_image): buffer = file.read() try: encoding = get_encoding(buffer) content = buffer.decode(encoding) return html2text(content) except BaseException as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) return f'{e}' ================================================ FILE: apps/common/handle/impl/text/pdf_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: text_split_handle.py @date:2024/3/27 18:19 @desc: """ import os import re import tempfile import time import traceback from typing import List import fitz from django.utils.translation import gettext_lazy as _ from common.handle.base_split_handle import BaseSplitHandle from common.utils.logger import maxkb_logger from common.utils.split_model import SplitModel, smart_split_paragraph default_pattern_list = [re.compile('(?<=^)# .*|(?<=\\n)# .*'), re.compile('(?<=\\n)(? 0: return {'name': file.name, 'content': result} # 没有目录的pdf content = self.handle_pdf_content(file, pdf_document) if pattern_list is not None and len(pattern_list) > 0: split_model = SplitModel(pattern_list, with_filter, limit) else: split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit) except BaseException as e: maxkb_logger.error(f"File: {file.name}, error: {e}, {traceback.format_exc()}") return { 'name': file.name, 'content': [] } finally: pdf_document.close() # 处理完后可以删除临时文件 os.remove(temp_file_path) return { 'name': file.name, 'content': split_model.parse(content) } @staticmethod def handle_pdf_content(file, pdf_document): # 第一步:收集所有字体大小 font_sizes = [] for page_num in range(len(pdf_document)): page = pdf_document.load_page(page_num) blocks = page.get_text("dict")["blocks"] for block in blocks: if block["type"] == 0: for line in block["lines"]: for span in line["spans"]: if span["size"] > 0: font_sizes.append(span["size"]) # 计算正文字体大小(众数) if not font_sizes: body_font_size = 12 else: from collections import Counter body_font_size = Counter(font_sizes).most_common(1)[0][0] # 第二步:提取内容 content = "" for page_num in range(len(pdf_document)): start_time = time.time() page = pdf_document.load_page(page_num) blocks = page.get_text("dict")["blocks"] for block in blocks: if block["type"] == 0: # 文本块 for line in block["lines"]: if not line["spans"]: continue text = "".join([span["text"] for span in line["spans"]]) font_size = line["spans"][0]["size"] # 根据与正文字体的差值判断 size_diff = font_size - body_font_size if size_diff > 2: # 明显大于正文 content += f"## {text}\n\n" elif size_diff > 0.5: # 略大于正文 content += f"### {text}\n\n" else: # 正文 content += f"{text}\n" elif block["type"] == 1: # 图片块 content += f"![image](image_{page_num}_{block['number']})\n\n" content = content.replace('\0', '') elapsed_time = time.time() - start_time maxkb_logger.debug( f"File: {file.name}, Page: {page_num + 1}, Time: {elapsed_time:.3f}s") return content @staticmethod def handle_toc(doc, limit): # 找到目录 toc = doc.get_toc() if toc is None or len(toc) == 0: return None # 创建存储章节内容的数组 chapters = [] # 遍历目录并按章节提取文本 for i, entry in enumerate(toc): level, title, start_page = entry start_page -= 1 # PyMuPDF 页码从 0 开始,书签页码从 1 开始 chapter_title = title # 确定结束页码,如果是最后一个章节则到文档末尾 if i + 1 < len(toc): end_page = toc[i + 1][2] - 1 else: end_page = doc.page_count - 1 # 去掉标题中的符号 title = PdfSplitHandle.handle_chapter_title(title) # 提取该章节的文本内容 chapter_text = "" for page_num in range(start_page, end_page + 1): page = doc.load_page(page_num) # 加载页面 text = page.get_text("text") text = re.sub(r'(? -1: text = text[idx + len(title):] if i + 1 < len(toc): l, next_title, next_start_page = toc[i + 1] next_title = PdfSplitHandle.handle_chapter_title(next_title) # print(f'next_title: {next_title}') idx = text.find(next_title) if idx > -1: text = text[:idx] chapter_text += text # 提取文本 # Null characters are not allowed. chapter_text = chapter_text.replace('\0', '') # 限制标题长度 real_chapter_title = chapter_title[:256] # 限制章节内容长度 if 0 < limit < len(chapter_text): split_text = smart_split_paragraph(chapter_text, limit) for text in split_text: chapters.append({"title": real_chapter_title, "content": text}) else: chapters.append( {"title": real_chapter_title, "content": chapter_text if chapter_text else real_chapter_title}) # 保存章节内容和章节标题 return chapters @staticmethod def handle_links(doc, pattern_list, with_filter, limit): # 检查文档是否包含内部链接 if not check_links_in_pdf(doc): return # 创建存储章节内容的数组 chapters = [] toc_start_page = -1 page_content = "" handle_pre_toc = True # 遍历 PDF 的每一页,查找带有目录链接的页 for page_num in range(doc.page_count): page = doc.load_page(page_num) links = page.get_links() # 如果目录开始页码未设置,则设置为当前页码 if len(links) > 0: toc_start_page = page_num if toc_start_page < 0: page_content += page.get_text('text') # 检查该页是否包含内部链接(即指向文档内部的页面) for num in range(len(links)): link = links[num] if link['kind'] == 1: # 'kind' 为 1 表示内部链接 # 获取链接目标的页面 dest_page = link['page'] rect = link['from'] # 获取链接的矩形区域 # 如果目录开始页码包括前言部分,则不处理前言部分 if dest_page < toc_start_page: handle_pre_toc = False # 提取链接区域的文本作为标题 link_title = page.get_text("text", clip=rect).strip().split("\n")[0].replace('.', '').strip() # print(f'link_title: {link_title}') # 提取目标页面内容作为章节开始 start_page = dest_page end_page = dest_page # 下一个link next_link = links[num + 1] if num + 1 < len(links) else None next_link_title = None if next_link is not None and next_link['kind'] == 1: rect = next_link['from'] next_link_title = page.get_text("text", clip=rect).strip() \ .split("\n")[0].replace('.', '').strip() # print(f'next_link_title: {next_link_title}') end_page = next_link['page'] # 提取章节内容 chapter_text = "" for p_num in range(start_page, end_page + 1): p = doc.load_page(p_num) text = p.get_text("text") text = re.sub(r'(? -1: text = text[idx + len(link_title):] if next_link_title is not None: idx = text.find(next_link_title) if idx > -1: text = text[:idx] chapter_text += text # Null characters are not allowed. chapter_text = chapter_text.replace('\0', '') # 限制章节内容长度 if 0 < limit < len(chapter_text): split_text = smart_split_paragraph(chapter_text, limit) for text in split_text: chapters.append({"title": link_title, "content": text}) else: # 保存章节信息 chapters.append({"title": link_title, "content": chapter_text}) # 目录中没有前言部分,手动处理 if handle_pre_toc: pre_toc = [] lines = page_content.strip().split('\n') try: for line in lines: if re.match(r'^前\s*言', line): pre_toc.append({'title': line, 'content': ''}) else: pre_toc[-1]['content'] += line for i in range(len(pre_toc)): pre_toc[i]['content'] = re.sub(r'(? 0: split_model = SplitModel(pattern_list, with_filter, limit) else: split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit) # 插入目录前的部分 page_content = re.sub(r'(? 0.5: return True return False def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): buffer = get_buffer(file) if type(limit) is str: limit = int(limit) if type(with_filter) is str: with_filter = with_filter.lower() == 'true' if pattern_list is not None and len(pattern_list) > 0: split_model = SplitModel(pattern_list, with_filter, limit) else: split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit) try: content = buffer.decode(detect(buffer)['encoding']) except BaseException as e: maxkb_logger.error(f"Error processing TEXT file {file.name}: {e}, {traceback.format_exc()}") return {'name': file.name, 'content': []} return {'name': file.name, 'content': split_model.parse(content)} def get_content(self, file, save_image): buffer = file.read() try: return buffer.decode(detect(buffer)['encoding']) except BaseException as e: traceback.print_exception(e) return f'{e}' ================================================ FILE: apps/common/handle/impl/text/xls_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: xls_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import traceback from typing import List import xlrd from common.handle.base_split_handle import BaseSplitHandle from common.utils.logger import maxkb_logger def post_cell(cell_value): return cell_value.replace('\r\n', '
').replace('\n', '
').replace('|', '|') def row_to_md(row): return '| ' + ' | '.join( [post_cell(str(cell)) if cell is not None else '' for cell in row]) + ' |\n' def handle_sheet(file_name, sheet, limit: int): rows = iter([sheet.row_values(i) for i in range(sheet.nrows)]) paragraphs = [] result = {'name': file_name, 'content': paragraphs} try: title_row_list = next(rows) title_md_content = row_to_md(title_row_list) title_md_content += '| ' + ' | '.join( ['---' if cell is not None else '' for cell in title_row_list]) + ' |\n' except Exception as e: return result if len(title_row_list) == 0: return result result_item_content = '' for row in rows: next_md_content = row_to_md(row) next_md_content_len = len(next_md_content) result_item_content_len = len(result_item_content) if len(result_item_content) == 0: result_item_content += title_md_content result_item_content += next_md_content else: if result_item_content_len + next_md_content_len < limit: result_item_content += next_md_content else: paragraphs.append({'content': result_item_content, 'title': ''}) result_item_content = title_md_content + next_md_content if len(result_item_content) > 0: paragraphs.append({'content': result_item_content, 'title': ''}) return result class XlsSplitHandle(BaseSplitHandle): def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): buffer = get_buffer(file) try: if type(limit) is str: limit = int(limit) workbook = xlrd.open_workbook(file_contents=buffer) worksheets = workbook.sheets() worksheets_size = len(worksheets) return [row for row in [handle_sheet(file.name, sheet, limit) if worksheets_size == 1 and sheet.name == 'Sheet1' else handle_sheet( sheet.name, sheet, limit) for sheet in worksheets] if row is not None] except Exception as e: maxkb_logger.error(f"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'content': []}] def get_content(self, file, save_image): # 打开 .xls 文件 try: workbook = xlrd.open_workbook(file_contents=file.read(), formatting_info=True) sheets = workbook.sheets() md_tables = '' for sheet in sheets: # 过滤空白的sheet if sheet.nrows == 0 or sheet.ncols == 0: continue # 获取表头和内容 headers = sheet.row_values(0) data = [sheet.row_values(row_idx) for row_idx in range(1, sheet.nrows)] # 构建 Markdown 表格 md_table = '| ' + ' | '.join(headers) + ' |\n' md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\n' for row in data: # 将每个单元格中的内容替换换行符为
以保留原始格式 md_table += '| ' + ' | '.join( [str(cell) .replace('\r\n', '
') .replace('\n', '
') if cell else '' for cell in row]) + ' |\n' md_tables += md_table + '\n\n' return md_tables except Exception as e: maxkb_logger.error(f'excel split handle error: {e}') return f'error: {e}' def support(self, file, get_buffer): file_name: str = file.name.lower() buffer = get_buffer(file) if file_name.endswith(".xls") and xlrd.inspect_format(content=buffer): return True return False ================================================ FILE: apps/common/handle/impl/text/xlsx_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: xlsx_parse_qa_handle.py @date:2024/5/21 14:59 @desc: """ import io import traceback from typing import List import openpyxl from openpyxl import load_workbook from common.handle.base_split_handle import BaseSplitHandle from common.handle.impl.common_handle import xlsx_embed_cells_images from common.utils.logger import maxkb_logger splitter = '\n`-----------------------------------`\n' def post_cell(image_dict, cell_value): image = image_dict.get(cell_value, None) if image is not None: return f'![](./oss/file/{image.id})' return cell_value.replace('\n', '
').replace('|', '|') def row_to_md(row, image_dict): return '| ' + ' | '.join( [post_cell(image_dict, str(cell.value if cell.value is not None else '')) if cell is not None else '' for cell in row]) + ' |\n' def handle_sheet(file_name, sheet, image_dict, limit: int): rows = sheet.rows paragraphs = [] result = {'name': file_name, 'content': paragraphs} try: title_row_list = next(rows) title_md_content = row_to_md(title_row_list, image_dict) title_md_content += '| ' + ' | '.join( ['---' if cell is not None else '' for cell in title_row_list]) + ' |\n' except Exception as e: return result if len(title_row_list) == 0: return result result_item_content = '' for row in rows: next_md_content = row_to_md(row, image_dict) next_md_content_len = len(next_md_content) result_item_content_len = len(result_item_content) if len(result_item_content) == 0: result_item_content += title_md_content result_item_content += next_md_content else: if result_item_content_len + next_md_content_len < limit: result_item_content += next_md_content else: paragraphs.append({'content': result_item_content, 'title': ''}) result_item_content = title_md_content + next_md_content if len(result_item_content) > 0: paragraphs.append({'content': result_item_content, 'title': ''}) return result class XlsxSplitHandle(BaseSplitHandle): def fill_merged_cells(self, sheet, image_dict): data = [] # 获取第一行作为标题行 headers = [] for idx, cell in enumerate(sheet[1]): if cell.value is None: headers.append(' ' * (idx + 1)) else: headers.append(cell.value) # 从第二行开始遍历每一行 for row in sheet.iter_rows(min_row=2, values_only=False): row_data = {} for col_idx, cell in enumerate(row): cell_value = cell.value # 如果单元格为空,并且该单元格在合并单元格内,获取合并单元格的值 if cell_value is None: for merged_range in sheet.merged_cells.ranges: if cell.coordinate in merged_range: cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value break image = image_dict.get(cell_value, None) if image is not None: cell_value = f'![](./oss/file/{image.id})' # 使用标题作为键,单元格的值作为值存入字典 row_data[headers[col_idx]] = cell_value data.append(row_data) return data def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): buffer = get_buffer(file) try: if type(limit) is str: limit = int(limit) workbook = openpyxl.load_workbook(io.BytesIO(buffer)) try: image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer)) save_image([item for item in image_dict.values()]) except Exception as e: image_dict = {} worksheets = workbook.worksheets worksheets_size = len(worksheets) return [row for row in [handle_sheet(file.name, sheet, image_dict, limit) if worksheets_size == 1 and sheet.title == 'Sheet1' else handle_sheet( sheet.title, sheet, image_dict, limit) for sheet in worksheets] if row is not None] except Exception as e: maxkb_logger.error(f"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}") return [{'name': file.name, 'content': []}] def get_content(self, file, save_image): try: # 加载 Excel 文件 workbook = load_workbook(file) try: image_dict: dict = xlsx_embed_cells_images(file) if len(image_dict) > 0: save_image(image_dict.values()) except Exception as e: maxkb_logger.error(f'Exception: {e}') image_dict = {} md_tables = '' # 遍历所有工作表 for sheetname in workbook.sheetnames: sheet = workbook[sheetname] rows = self.fill_merged_cells(sheet, image_dict) if len(rows) == 0: continue # 添加 sheet 名称作为标题 md_tables += f'## {sheetname}\n\n' # 提取表头和内容 headers = [f"{key}" for key, value in rows[0].items()] # 构建 Markdown 表格 md_table = '| ' + ' | '.join(headers) + ' |\n' md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\n' for row in rows: r = [self._escape_cell_content(value) for key, value in row.items()] md_table += '| ' + ' | '.join(r) + ' |\n' md_tables += md_table + '\n\n' return md_tables except Exception as e: maxkb_logger.error(f'excel split handle error: {e}') return f'error: {e}' def _escape_cell_content(self, cell_value): """转义单元格内容,避免破坏 Markdown 表格结构""" if cell_value is None: return '' cell_str = str(cell_value) # 替换换行符为
cell_str = cell_str.replace('\n', '
') # 转义管道符 | 为 HTML 实体 cell_str = cell_str.replace('|', '|') # 如果内容包含反引号,需要转义 if '`' in cell_str: cell_str = cell_str.replace('`', '`') return cell_str def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".xlsx"): return True return False ================================================ FILE: apps/common/handle/impl/text/zip_split_handle.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: text_split_handle.py @date:2024/3/27 18:19 @desc: """ import io import os import re import zipfile from typing import List from urllib.parse import urljoin import uuid_utils.compat as uuid from charset_normalizer import detect from django.utils.translation import gettext_lazy as _ from common.handle.base_split_handle import BaseSplitHandle from common.handle.impl.text.csv_split_handle import CsvSplitHandle from common.handle.impl.text.doc_split_handle import DocSplitHandle from common.handle.impl.text.html_split_handle import HTMLSplitHandle from common.handle.impl.text.pdf_split_handle import PdfSplitHandle from common.handle.impl.text.text_split_handle import TextSplitHandle from common.handle.impl.text.xls_split_handle import XlsSplitHandle from common.handle.impl.text.xlsx_split_handle import XlsxSplitHandle from common.utils.common import parse_md_image from knowledge.models import File class FileBufferHandle: buffer = None def get_buffer(self, file): if self.buffer is None: self.buffer = file.read() return self.buffer default_split_handle = TextSplitHandle() split_handles = [ HTMLSplitHandle(), DocSplitHandle(), PdfSplitHandle(), XlsxSplitHandle(), XlsSplitHandle(), CsvSplitHandle(), default_split_handle ] def file_to_paragraph(file, pattern_list: List, with_filter: bool, limit: int, save_inner_image): get_buffer = FileBufferHandle().get_buffer for split_handle in split_handles: if split_handle.support(file, get_buffer): return split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_inner_image) raise Exception(_('Unsupported file format')) def is_valid_uuid(uuid_str: str): try: uuid.UUID(uuid_str) except ValueError: return False return True def get_image_list(result_list: list, zip_files: List[str]): image_file_list = [] for result in result_list: for p in result.get('content', []): content: str = p.get('content', '') image_list = parse_md_image(content) for image in image_list: search = re.search("\(.*\)", image) if search: new_image_id = str(uuid.uuid7()) source_image_path = search.group().replace('(', '').replace(')', '') source_image_path = source_image_path.strip().split(" ")[0] image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith( '/') else source_image_path) if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') p['content'] = content else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') p['content'] = content return image_file_list def get_image_list_by_content(name: str, content: str, zip_files: List[str]): image_file_list = [] image_list = parse_md_image(content) for image in image_list: search = re.search("\(.*\)", image) if search: new_image_id = str(uuid.uuid7()) source_image_path = search.group().replace('(', '').replace(')', '') source_image_path = source_image_path.strip().split(" ")[0] image_path = urljoin(name, '.' + source_image_path if source_image_path.startswith( '/') else source_image_path) if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') else: image_file_list.append({'source_file': image_path, 'image_id': new_image_id}) content = content.replace(source_image_path, f'./oss/file/{new_image_id}') return image_file_list, content def get_file_name(file_name): try: file_name_code = file_name.encode('cp437') charset = detect(file_name_code)['encoding'] return file_name_code.decode(charset) except Exception as e: return file_name def filter_image_file(result_list: list, image_list): image_source_file_list = [image.get('source_file') for image in image_list] return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))] class ZipSplitHandle(BaseSplitHandle): def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image): if type(limit) is str: limit = int(limit) if type(with_filter) is str: with_filter = with_filter.lower() == 'true' buffer = get_buffer(file) bytes_io = io.BytesIO(buffer) result = [] # 打开zip文件 with zipfile.ZipFile(bytes_io, 'r') as zip_ref: # 获取压缩包中的文件名列表 files = zip_ref.namelist() # 读取压缩包中的文件内容 for file in files: if file.endswith('/') or file.startswith('__MACOSX'): continue with zip_ref.open(file) as f: # 对文件内容进行处理 try: # 处理一下文件名 f.name = get_file_name(f.name) value = file_to_paragraph(f, pattern_list, with_filter, limit, save_image) if isinstance(value, list): result = [*result, *value] else: result.append(value) except Exception: pass image_list = get_image_list(result, files) result = filter_image_file(result, image_list) image_mode_list = [] for image in image_list: with zip_ref.open(image.get('source_file')) as f: i = File( id=image.get('image_id'), file_name=os.path.basename(image.get('source_file')), meta={'debug': False, 'content': f.read()} # 这里的content是二进制数据 ) image_mode_list.append(i) save_image(image_mode_list) return result def support(self, file, get_buffer): file_name: str = file.name.lower() if file_name.endswith(".zip") or file_name.endswith(".ZIP"): return True return False def get_content(self, file, save_image): """ 从 zip 中提取并返回拼接的 md 文本,同时收集并保存内嵌图片(通过 save_image 回调)。 使用 posixpath 来正确处理 zip 内部的路径拼接与规范化。 """ buffer = file.read() if hasattr(file, 'read') else None bytes_io = io.BytesIO(buffer) if buffer is not None else io.BytesIO(file) image_list = [] content_parts = [] with zipfile.ZipFile(bytes_io, 'r') as zip_ref: files = zip_ref.namelist() file_content_list = [] for inner_name in files: if inner_name.endswith('/') or inner_name.startswith('__MACOSX'): continue with zip_ref.open(inner_name) as zf: try: real_name = get_file_name(zf.name) except Exception: real_name = zf.name # 为 split_handle 提供可重复读取的 file-like 对象 zf.name = real_name get_buffer = FileBufferHandle().get_buffer for split_handle in split_handles: if split_handle.support(zf, get_buffer): row = get_buffer(zf) md_text = split_handle.get_content(io.BytesIO(row), save_image) file_content_list.append({'content': md_text, 'name': real_name}) break for file_content in file_content_list: _image_list, content = get_image_list_by_content(file_content.get('name'), file_content.get("content"), files) content_parts.append(content) for image in _image_list: image_list.append(image) # 将收集到的图片通过回调保存(一次性) if image_list: image_mode_list = [] for image in image_list: with zip_ref.open(image.get('source_file')) as f: i = File( id=image.get('image_id'), file_name=os.path.basename(image.get('source_file')), meta={'debug': False, 'content': f.read()} # 这里的content是二进制数据 ) image_mode_list.append(i) save_image(image_mode_list) return '\n\n'.join(content_parts) ================================================ FILE: apps/common/init/init_doc.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: init_doc.py @date:2024/5/24 14:11 @desc: """ import hashlib from django.urls import path, URLPattern from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView from maxkb.const import CONFIG chat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/' def init_app_doc(system_urlpatterns): system_urlpatterns += [ path(f'{CONFIG.get_admin_path()[1:]}/api-doc/schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的 path(f'{CONFIG.get_admin_path()[1:]}/api-doc/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), # swagger-ui的路由 ] class ChatSpectacularSwaggerView(SpectacularSwaggerView): @staticmethod def _swagger_ui_resource(filename): return f'{CONFIG.get_chat_path()}/api-doc/swagger-ui-dist/{filename}' @staticmethod def _swagger_ui_favicon(): return f'{CONFIG.get_chat_path()}/api-doc/swagger-ui-dist/favicon-32x32.png' def init_chat_doc(system_urlpatterns, chat_urlpatterns): system_urlpatterns += [ path(f'{CONFIG.get_chat_path()[1:]}/api-doc/schema/', SpectacularAPIView.as_view(patterns=[ URLPattern(pattern=f'{chat_api_prefix}{str(url.pattern)}', callback=url.callback, default_args=url.default_args, name=url.name) for url in chat_urlpatterns if ['chat', 'open', 'profile'].__contains__(url.name)]), name='chat_schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的 path(f'{CONFIG.get_chat_path()[1:]}/api-doc/', ChatSpectacularSwaggerView.as_view(url_name='chat_schema'), name='swagger-ui'), # swagger-ui的路由 ] def encrypt(text): md5 = hashlib.md5() md5.update(text.encode()) result = md5.hexdigest() return result def get_call(application_urlpatterns, patterns, params, func): def run(): if params['valid'](): func(*params['get_params'](application_urlpatterns, patterns)) return run init_list = [(init_app_doc, {'valid': lambda: CONFIG.get('DOC_PASSWORD') is not None and encrypt( CONFIG.get('DOC_PASSWORD')) == 'd4fc097197b4b90a122b92cbd5bbe867', 'get_call': get_call, 'get_params': lambda application_urlpatterns, patterns: (application_urlpatterns,)}), (init_chat_doc, {'valid': lambda: CONFIG.get('DOC_PASSWORD') is not None and encrypt( CONFIG.get('DOC_PASSWORD')) == 'd4fc097197b4b90a122b92cbd5bbe867' or True, 'get_call': get_call, 'get_params': lambda application_urlpatterns, patterns: ( application_urlpatterns, patterns)})] def init_doc(system_urlpatterns, chat_patterns): for init, params in init_list: if params['valid'](): get_call(system_urlpatterns, chat_patterns, params, init)() ================================================ FILE: apps/common/init/init_template.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: init_jinja.py @date:2025/12/1 17:16 @desc: """ from typing import Any from jinja2.sandbox import SandboxedEnvironment from langchain_core.prompts.string import DEFAULT_FORMATTER_MAPPING, _HAS_JINJA2 def jinja2_formatter(template: str, /, **kwargs: Any) -> str: """Format a template using jinja2. *Security warning*: As of LangChain 0.0.329, this method uses Jinja2's SandboxedEnvironment by default. However, this sand-boxing should be treated as a best-effort approach rather than a guarantee of security. Do not accept jinja2 templates from untrusted sources as they may lead to arbitrary Python code execution. https://jinja.palletsprojects.com/en/3.1.x/sandbox/ Args: template: The template string. **kwargs: The variables to format the template with. Returns: The formatted string. Raises: ImportError: If jinja2 is not installed. """ if not _HAS_JINJA2: msg = ( "jinja2 not installed, which is needed to use the jinja2_formatter. " "Please install it with `pip install jinja2`." "Please be cautious when using jinja2 templates. " "Do not expand jinja2 templates using unverified or user-controlled " "inputs as that can result in arbitrary Python code execution." ) raise ImportError(msg) # Use a restricted sandbox that blocks ALL attribute/method access # Only simple variable lookups like {{variable}} are allowed # Attribute access like {{variable.attr}} or {{variable.method()}} is blocked return SandboxedEnvironment().from_string(template).render(**kwargs) def run(): DEFAULT_FORMATTER_MAPPING['jinja2'] = jinja2_formatter ================================================ FILE: apps/common/job/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2024/3/14 11:54 @desc: """ from .clean_chat_job import * from .clean_debug_file_job import * from .client_access_num_job import * def run(): # client_access_num_job.run() clean_chat_job.run() clean_debug_file_job.run() client_access_num_job.run() ================================================ FILE: apps/common/job/clean_chat_job.py ================================================ # coding=utf-8 import datetime from django.db import transaction from django.db.models import Q, Max from django.utils import timezone from application.models import Application, Chat, ChatRecord from common.job.scheduler import scheduler from common.utils.lock import lock, RedisLock from common.utils.logger import maxkb_logger from knowledge.models import File def clean_chat_log_job(): clean_chat_log_job_lock() @lock(lock_key='clean_chat_log_job_execute', timeout=30) def clean_chat_log_job_lock(): from django.utils.translation import gettext_lazy as _ maxkb_logger.info(_('start clean chat log')) now = timezone.now() applications = Application.objects.all().values('id', 'clean_time', 'file_clean_time') cutoff_dates = { app['id']: now - datetime.timedelta(days=app['clean_time'] or 180) for app in applications } file_cutoff_dates = { app['id']: now - datetime.timedelta(days=app['file_clean_time'] or app['clean_time'] or 180) for app in applications } file_conditions = Q() for app_id, cutoff_date in file_cutoff_dates.items(): file_conditions |= Q(chat__application_id=app_id, create_time__lt=cutoff_date) clean_method(file_conditions, clean_log=False) query_conditions = Q() for app_id, cutoff_date in cutoff_dates.items(): query_conditions |= Q(chat__application_id=app_id, create_time__lt=cutoff_date) clean_method(query_conditions) maxkb_logger.info(_('end clean chat log')) def clean_method(query_conditions, clean_log=True): batch_size = 500 while True: with transaction.atomic(): chat_records = ChatRecord.objects.filter(query_conditions).select_related('chat').only('id', 'chat_id', 'create_time')[ :batch_size] if not chat_records: break chat_record_ids = [record.id for record in chat_records] chat_ids = {record.chat_id for record in chat_records} # 计算每个 chat_id 的最大 create_time max_create_times = ChatRecord.objects.filter(id__in=chat_record_ids).values('chat_id').annotate( max_create_time=Max('create_time')) # 收集需要删除的文件 files_to_delete = [] for record in chat_records: max_create_time = next( (item['max_create_time'] for item in max_create_times if str(item['chat_id']) == str(record.chat_id)), None) if max_create_time: files_to_delete.extend( File.objects.filter(source_id=str(record.chat_id), create_time__lt=max_create_time) ) # 删除 ChatRecord deleted_count = 0 if clean_log: deleted_count = ChatRecord.objects.filter(id__in=chat_record_ids).delete()[0] from django.db.models import Count updated_counts = ChatRecord.objects.filter(chat_id__in=chat_ids) \ .values('chat_id') \ .annotate(count=Count('id')) count_map = {item['chat_id']: item['count'] for item in updated_counts} for chat_id in chat_ids: count = count_map.get(chat_id, 0) # 如果没有记录则为0 Chat.objects.filter(id=chat_id).update(chat_record_count=count) # 删除没有关联 ChatRecord 的 Chat Chat.objects.filter(chatrecord__isnull=True, id__in=chat_ids).delete() File.objects.filter(loid__in=[file.loid for file in files_to_delete]).delete() if deleted_count < batch_size: break def run(): rlock = RedisLock() if rlock.try_lock('clean_chat_log_job', 30 * 30): try: maxkb_logger.debug('get lock clean_chat_log_job') existing_job = scheduler.get_job(job_id='clean_chat_log') if existing_job is not None: existing_job.remove() scheduler.add_job(clean_chat_log_job, 'cron', hour='0', minute='5', id='clean_chat_log') finally: rlock.un_lock('clean_chat_log_job') ================================================ FILE: apps/common/job/clean_debug_file_job.py ================================================ # coding=utf-8 from datetime import timedelta from django.db.models import Q from django.utils import timezone from common.job.scheduler import scheduler from common.utils.lock import lock, RedisLock from common.utils.logger import maxkb_logger from knowledge.models import File, FileSourceType def clean_debug_file(): clean_debug_file_lock() @lock(lock_key='clean_debug_file_execute', timeout=30) def clean_debug_file_lock(): from django.utils.translation import gettext_lazy as _ maxkb_logger.debug(_('start clean debug file')) minutes_30_ago = timezone.now() - timedelta(minutes=30) two_hours_ago = timezone.now() - timedelta(hours=2) one_days_ago = timezone.now() - timedelta(hours=24) # 删除对应的文件 File.objects.filter( Q(create_time__lt=one_days_ago, source_type=FileSourceType.TEMPORARY_1_DAY.value) | Q(create_time__lt=two_hours_ago, source_type=FileSourceType.TEMPORARY_120_MINUTE.value) | Q(create_time__lt=minutes_30_ago, source_type=FileSourceType.TEMPORARY_30_MINUTE.value) ).delete() maxkb_logger.debug(_('end clean debug file')) def run(): rlock = RedisLock() if rlock.try_lock('clean_debug_file', 30 * 30): try: maxkb_logger.debug('get lock clean_debug_file') clean_debug_file_job = scheduler.get_job(job_id='clean_debug_file') if clean_debug_file_job is not None: clean_debug_file_job.remove() scheduler.add_job(clean_debug_file, 'cron', hour='*', minute='*/30', second='0', id='clean_debug_file') finally: rlock.un_lock('clean_debug_file') ================================================ FILE: apps/common/job/client_access_num_job.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: client_access_num_job.py @date:2024/3/14 11:56 @desc: """ from django.db.models import QuerySet from application.models import ApplicationChatUserStats from common.job.scheduler import scheduler from common.utils.lock import lock, RedisLock from common.utils.logger import maxkb_logger def client_access_num_reset_job(): client_access_num_reset_job_lock() @lock(lock_key="access_num_reset_execute", timeout=30) def client_access_num_reset_job_lock(): from django.utils.translation import gettext_lazy as _ maxkb_logger.info(_('start reset access_num')) QuerySet(ApplicationChatUserStats).update(intraday_access_num=0) maxkb_logger.info(_('end reset access_num')) def run(): rlock = RedisLock() if rlock.try_lock('access_num_reset', 30 * 30): try: maxkb_logger.debug('get lock access_num_reset') access_num_reset = scheduler.get_job(job_id='access_num_reset') if access_num_reset is not None: access_num_reset.remove() scheduler.add_job(client_access_num_reset_job, 'cron', hour='0', minute='0', second='0', id='access_num_reset') finally: rlock.un_lock('access_num_reset') ================================================ FILE: apps/common/job/scheduler.py ================================================ from apscheduler.schedulers.background import BackgroundScheduler from django_apscheduler.jobstores import DjangoJobStore scheduler = BackgroundScheduler() scheduler.add_jobstore(DjangoJobStore(), "default") try: scheduler.start() except Exception as e: from common.utils.logger import maxkb_logger maxkb_logger.error(f"Failed to start scheduler: {e}") ================================================ FILE: apps/common/lock/__init__.py ================================================ ================================================ FILE: apps/common/lock/base_lock.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: base_lock.py @date:2024/8/20 10:33 @desc: """ from abc import ABC, abstractmethod class BaseLock(ABC): @abstractmethod def try_lock(self, key, timeout): pass @abstractmethod def un_lock(self, key): pass ================================================ FILE: apps/common/lock/impl/__init__.py ================================================ ================================================ FILE: apps/common/log/__init__.py ================================================ ================================================ FILE: apps/common/log/log.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: log.py @date:2025/6/4 14:13 @desc: """ from system_manage.models.log_management import Log def _get_ip_address(request): """ 获取ip地址 @param request: @return: """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: ip = request.META.get('REMOTE_ADDR') return ip def _get_user(request): """ 获取用户 @param request: @return: """ user = request.user if user is None: return { } user_info = { "id": str(user.id), "email": user.email, "phone": user.phone, "nick_name": user.nick_name, "username": user.username, } # 如果是 User 模型且有 role 属性 if hasattr(user, 'role'): user_info['role'] = user.role return user_info def _get_details(request): path = request.path body = request.data query = request.query_params return { 'path': path, 'body': body, 'query': query } def _get_workspace_id(request, kwargs): return kwargs.get('workspace_id', 'None') def log(menu: str, operate, get_user=_get_user, get_ip_address=_get_ip_address, get_details=_get_details, get_operation_object=None, get_workspace_id=_get_workspace_id): """ 记录审计日志 @param menu: 操作菜单 str @param operate: 操作 str|func 如果是一个函数 入参将是一个request 响应为str def operate(request): return "操作菜单" @param get_user: 获取用户 @param get_ip_address:获取IP地址 @param get_details: 获取执行详情 @param get_operation_object: 获取操作对象 @param get_workspace_id: 获取工作空间id @return: """ def inner(func): def run(view, request, **kwargs): status = 200 operation_object = {} try: if get_operation_object is not None: operation_object = get_operation_object(request, kwargs) except Exception as e: pass try: return func(view, request, **kwargs) except Exception as e: status = 500 raise e finally: ip = get_ip_address(request) user = get_user(request) details = get_details(request) workspace_id = get_workspace_id(request, kwargs) _operate = operate if callable(operate): _operate = operate(request) # 插入审计日志 Log(menu=menu, operate=_operate, user=user, status=status, ip_address=ip, details=details, operation_object=operation_object, workspace_id=workspace_id).save() return run return inner ================================================ FILE: apps/common/management/__init__.py ================================================ ================================================ FILE: apps/common/management/commands/__init__.py ================================================ ================================================ FILE: apps/common/management/commands/celery.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: celery.py @date:2024/8/19 11:57 @desc: """ import os import subprocess from django.core.management.base import BaseCommand from maxkb.const import BASE_DIR class Command(BaseCommand): help = 'celery' def add_arguments(self, parser): parser.add_argument( 'service', nargs='+', type=str, choices=("celery", "model"), help='Service', ) def handle(self, *args, **options): service = options.get('service') os.environ.setdefault('CELERY_NAME', ','.join(service)) server_hostname = os.environ.get("SERVER_HOSTNAME") if hasattr(os, 'getuid') and os.getuid() == 0: os.environ.setdefault('C_FORCE_ROOT', '1') if not server_hostname: server_hostname = '%h' cmd = [ 'celery', '-A', 'ops', 'worker', '-P', 'threads', '-l', 'info', '-c', '10', '-Q', ','.join(service), '--heartbeat-interval', '10', '-n', f'{",".join(service)}@{server_hostname}', '--without-mingle', ] kwargs = {'cwd': BASE_DIR} subprocess.run(cmd, **kwargs) ================================================ FILE: apps/common/management/commands/restart.py ================================================ from .services.command import BaseActionCommand, Action class Command(BaseActionCommand): help = 'Restart services' action = Action.restart.value ================================================ FILE: apps/common/management/commands/services/__init__.py ================================================ ================================================ FILE: apps/common/management/commands/services/command.py ================================================ import math import os from django.core.management.base import BaseCommand from django.db.models import TextChoices from .utils import ServicesUtil class Services(TextChoices): gunicorn = 'gunicorn', 'gunicorn' celery_default = 'celery_default', 'celery_default' local_model = 'local_model', 'local_model' web = 'web', 'web' celery = 'celery', 'celery' celery_model = 'celery_model', 'celery_model' task = 'task', 'task' all = 'all', 'all' @classmethod def get_service_object_class(cls, name): from . import services services_map = { cls.gunicorn.value: services.GunicornService, cls.celery_default: services.CeleryDefaultService, cls.local_model: services.GunicornLocalModelService, } return services_map.get(name) @classmethod def web_services(cls): return [cls.gunicorn, cls.local_model] @classmethod def celery_services(cls): return [cls.celery_default, cls.celery_model] @classmethod def task_services(cls): return cls.celery_services() @classmethod def all_services(cls): return cls.web_services() + cls.task_services() @classmethod def export_services_values(cls): return [cls.all.value, cls.web.value, cls.task.value] + [s.value for s in cls.all_services()] @classmethod def get_service_objects(cls, service_names, **kwargs): services = set() for name in service_names: method_name = f'{name}_services' if hasattr(cls, method_name): _services = getattr(cls, method_name)() elif hasattr(cls, name): _services = [getattr(cls, name)] else: continue services.update(set(_services)) service_objects = [] for s in services: service_class = cls.get_service_object_class(s.value) if not service_class: continue kwargs.update({ 'name': s.value }) service_object = service_class(**kwargs) service_objects.append(service_object) return service_objects class Action(TextChoices): start = 'start', 'start' status = 'status', 'status' stop = 'stop', 'stop' restart = 'restart', 'restart' class BaseActionCommand(BaseCommand): help = 'Service Base Command' action = None util = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def add_arguments(self, parser): parser.add_argument( 'services', nargs='+', choices=Services.export_services_values(), help='Service', ) parser.add_argument('-d', '--daemon', nargs="?", const=True) parser.add_argument('-w', '--worker', type=int, nargs="?", default=3 if os.cpu_count() > 6 else max(1, math.floor(os.cpu_count() / 2))) parser.add_argument('-f', '--force', nargs="?", const=True) def initial_util(self, *args, **options): service_names = options.get('services') service_kwargs = { 'worker_gunicorn': options.get('worker') } services = Services.get_service_objects(service_names=service_names, **service_kwargs) kwargs = { 'services': services, 'run_daemon': options.get('daemon', False), 'stop_daemon': self.action == Action.stop.value and Services.all.value in service_names, 'force_stop': options.get('force') or False, } self.util = ServicesUtil(**kwargs) def handle(self, *args, **options): self.initial_util(*args, **options) assert self.action in Action.values, f'The action {self.action} is not in the optional list' _handle = getattr(self, f'_handle_{self.action}', lambda: None) _handle() def _handle_start(self): self.util.start_and_watch() os._exit(0) def _handle_stop(self): self.util.stop() def _handle_restart(self): self.util.restart() def _handle_status(self): self.util.show_status() ================================================ FILE: apps/common/management/commands/services/hands.py ================================================ import logging import os import sys from maxkb.const import CONFIG, PROJECT_DIR, LOG_DIR try: from apps.maxkb import const __version__ = const.VERSION except ImportError as e: print("Not found __version__: {}".format(e)) print("Python is: ") logging.info(sys.executable) __version__ = 'Unknown' sys.exit(1) HTTP_HOST = '0.0.0.0' HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 DEBUG = CONFIG.DEBUG or False APPS_DIR = os.path.join(PROJECT_DIR, 'apps') TMP_DIR = os.path.join(PROJECT_DIR, 'tmp') if not os.path.exists(TMP_DIR): os.makedirs(TMP_DIR, 0o700, exist_ok=True) ================================================ FILE: apps/common/management/commands/services/services/__init__.py ================================================ from .celery_default import * from .gunicorn import * from .local_model import * from .scheduler import * ================================================ FILE: apps/common/management/commands/services/services/base.py ================================================ import abc import time import shutil import psutil import datetime import threading import subprocess from ..hands import * class BaseService(object): def __init__(self, **kwargs): self.name = kwargs['name'] self._process = None self.STOP_TIMEOUT = 10 self.max_retry = 0 self.retry = 3 self.LOG_KEEP_DAYS = int(CONFIG.get('LOG_RETENTION_DAYS', 7)) self.EXIT_EVENT = threading.Event() @property @abc.abstractmethod def cmd(self): return [] @property @abc.abstractmethod def cwd(self): return '' @property def is_running(self): if self.pid == 0: return False try: os.kill(self.pid, 0) except (OSError, ProcessLookupError): return False else: return True def show_status(self): if self.is_running: msg = f'{self.name} is running: {self.pid}.' else: msg = f'{self.name} is stopped.' if DEBUG: msg = '\033[31m{} is stopped.\033[0m\nYou can manual start it to find the error: \n' \ ' $ cd {}\n' \ ' $ {}'.format(self.name, self.cwd, ' '.join(self.cmd)) print(msg) # -- log -- @property def log_filename(self): return f'{self.name}.log' @property def log_filepath(self): return os.path.join(LOG_DIR, self.log_filename) @property def log_file(self): return open(self.log_filepath, 'a') @property def log_dir(self): return os.path.dirname(self.log_filepath) # -- end log -- # -- pid -- @property def pid_filepath(self): return os.path.join(TMP_DIR, f'{self.name}.pid') @property def pid(self): if not os.path.isfile(self.pid_filepath): return 0 with open(self.pid_filepath) as f: try: pid = int(f.read().strip()) except ValueError: pid = 0 return pid def write_pid(self): with open(self.pid_filepath, 'w') as f: f.write(str(self.process.pid)) def remove_pid(self): if os.path.isfile(self.pid_filepath): os.unlink(self.pid_filepath) # -- end pid -- # -- process -- @property def process(self): if not self._process: try: self._process = psutil.Process(self.pid) except: pass return self._process # -- end process -- # -- action -- def open_subprocess(self): kwargs = {'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file} self._process = subprocess.Popen(self.cmd, **kwargs) def start(self): if self.is_running: self.show_status() return self.remove_pid() self.open_subprocess() self.write_pid() self.start_other() def start_other(self): pass def stop(self, force=False): if not self.is_running: self.show_status() # self.remove_pid() return print(f'Stop service: {self.name}', end='') sig = 9 if force else 15 os.kill(self.pid, sig) if self.process is None: print("\033[31m No process found\033[0m") return try: self.process.wait(1) except: pass for i in range(self.STOP_TIMEOUT): if i == self.STOP_TIMEOUT - 1: print("\033[31m Error\033[0m") if not self.is_running: print("\033[32m Ok\033[0m") self.remove_pid() break else: continue def watch(self): self._check() if not self.is_running: self._restart() self._rotate_log() def _check(self): now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') if self.process: try: self.process.wait(1) # 不wait,子进程可能无法回收 except: pass if self.is_running: logging.debug(f"{now} Check service status: {self.name} -> running at {self.pid}") else: logging.debug(f"{now} Check service status: {self.name} -> stopped at {self.pid}") def _restart(self): if self.retry > self.max_retry: logging.info("Service start failed, exit: {}".format(self.name)) self.EXIT_EVENT.set() return self.retry += 1 logging.info(f'> Find {self.name} stopped, retry {self.retry}, {self.pid}') self.start() def _rotate_log(self): now = datetime.datetime.now() _time = now.strftime('%H:%M') if _time != '23:59': return backup_date = now.strftime('%Y-%m-%d') backup_log_dir = os.path.join(self.log_dir, backup_date) if not os.path.exists(backup_log_dir): os.mkdir(backup_log_dir) backup_log_path = os.path.join(backup_log_dir, self.log_filename) if os.path.isfile(self.log_filepath) and not os.path.isfile(backup_log_path): logging.info(f'Rotate log file: {self.log_filepath} => {backup_log_path}') shutil.copy(self.log_filepath, backup_log_path) with open(self.log_filepath, 'w') as f: pass to_delete_date = now - datetime.timedelta(days=self.LOG_KEEP_DAYS) to_delete_dir = os.path.join(LOG_DIR, to_delete_date.strftime('%Y-%m-%d')) if os.path.exists(to_delete_dir): logging.info(f'Remove old log: {to_delete_dir}') shutil.rmtree(to_delete_dir, ignore_errors=True) # -- end action -- ================================================ FILE: apps/common/management/commands/services/services/celery_base.py ================================================ from django.conf import settings from .base import BaseService from ..hands import * class CeleryBaseService(BaseService): def __init__(self, queue, num=10, **kwargs): super().__init__(**kwargs) self.queue = queue self.num = num @property def cmd(self): print('\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize())) os.environ.setdefault('LC_ALL', 'C.UTF-8') os.environ.setdefault('PYTHONOPTIMIZE', '1') os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True') os.environ.setdefault('PYTHONPATH', settings.APPS_DIR) if os.getuid() == 0: os.environ.setdefault('C_FORCE_ROOT', '1') server_hostname = os.environ.get("SERVER_HOSTNAME") if not server_hostname: server_hostname = '%h' cmd = [ 'celery', '-A', 'ops', 'worker', '-P', 'threads', '-l', 'error', '-c', str(self.num), '-Q', self.queue, '--heartbeat-interval', '10', '-n', f'{self.queue}@{server_hostname}', '--without-mingle', ] return cmd @property def cwd(self): return APPS_DIR ================================================ FILE: apps/common/management/commands/services/services/celery_default.py ================================================ import os import subprocess from .celery_base import CeleryBaseService from django.conf import settings __all__ = ['CeleryDefaultService'] class CeleryDefaultService(CeleryBaseService): def __init__(self, **kwargs): kwargs['queue'] = 'celery' super().__init__(**kwargs) def open_subprocess(self): env = os.environ.copy() env['LC_ALL'] = 'C.UTF-8' env['PYTHONOPTIMIZE'] = '1' env['ANSIBLE_FORCE_COLOR'] = 'True' env['PYTHONPATH'] = settings.APPS_DIR env['SERVER_NAME'] = 'celery' if os.getuid() == 0: env.setdefault('C_FORCE_ROOT', '1') kwargs = { 'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file, 'env': env } self._process = subprocess.Popen(self.cmd, **kwargs) ================================================ FILE: apps/common/management/commands/services/services/gunicorn.py ================================================ import subprocess from .base import BaseService from ..hands import * __all__ = ['GunicornService'] class GunicornService(BaseService): def __init__(self, **kwargs): self.worker = kwargs['worker_gunicorn'] super().__init__(**kwargs) @property def cmd(self): print("\n- Start Gunicorn WSGI HTTP Server") log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'{HTTP_HOST}:{HTTP_PORT}' max_requests = 10240 if int(self.worker) > 1 else 0 cmd = [ 'gunicorn', 'maxkb.wsgi:application', '-b', bind, '-k', 'gthread', '--threads', '200', '-w', str(self.worker), '--max-requests', str(max_requests), '--max-requests-jitter', '2048', '--timeout', '30', '--graceful-timeout', '300', '--access-logformat', log_format, '--access-logfile', '/dev/null', '--error-logfile', '-' ] if DEBUG: cmd.append('--reload') return cmd @property def cwd(self): return APPS_DIR def open_subprocess(self): # 复制当前环境变量,并设置 ENABLE_SCHEDULER=1 env = os.environ.copy() env['SERVER_NAME'] = 'web' kwargs = { 'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file, 'env': env } self._process = subprocess.Popen(self.cmd, **kwargs) ================================================ FILE: apps/common/management/commands/services/services/local_model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: local_model.py @date:2024/8/21 13:28 @desc: """ import subprocess from maxkb.const import CONFIG from .base import BaseService from ..hands import * __all__ = ['GunicornLocalModelService'] class GunicornLocalModelService(BaseService): def __init__(self, **kwargs): self.worker = kwargs['worker_gunicorn'] super().__init__(**kwargs) @property def cmd(self): print("\n- Start Gunicorn Local Model WSGI HTTP Server") log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' worker = CONFIG.get("LOCAL_MODEL_HOST_WORKER", 1) max_requests = 10240 if int(worker) > 1 else 0 cmd = [ 'gunicorn', 'maxkb.wsgi:application', '-b', bind, '-k', 'gthread', '--threads', '200', '-w', str(worker), '--max-requests', str(max_requests), '--max-requests-jitter', '2048', '--timeout', '30', '--graceful-timeout', '300', '--access-logformat', log_format, '--access-logfile', '/dev/null', '--error-logfile', '-' ] if DEBUG: cmd.append('--reload') return cmd @property def cwd(self): return APPS_DIR def open_subprocess(self): # 复制当前环境变量,并设置 ENABLE_SCHEDULER=1 env = os.environ.copy() env['SERVER_NAME'] = 'local_model' kwargs = { 'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file, 'env': env } self._process = subprocess.Popen(self.cmd, **kwargs) ================================================ FILE: apps/common/management/commands/services/services/scheduler.py ================================================ import subprocess from .base import BaseService from ..hands import * __all__ = ['SchedulerService'] class SchedulerService(BaseService): def __init__(self, **kwargs): self.worker = 1 super().__init__(**kwargs) @property def cmd(self): print("\n- Start Scheduler Server") log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'127.0.0.1:6060' max_requests = 10240 if int(self.worker) > 1 else 0 cmd = [ 'gunicorn', 'maxkb.wsgi:application', '-b', bind, '-k', 'gthread', '--threads', '200', '-w', str(self.worker), '--max-requests', str(max_requests), '--max-requests-jitter', '2048', '--timeout', '30', '--graceful-timeout', '300', '--access-logformat', log_format, '--access-logfile', '/dev/null', '--error-logfile', '-' ] if DEBUG: cmd.append('--reload') return cmd @property def cwd(self): return APPS_DIR def open_subprocess(self): # 复制当前环境变量,并设置 ENABLE_SCHEDULER=1 env = os.environ.copy() env['ENABLE_SCHEDULER'] = '1' kwargs = { 'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file, 'env': env } self._process = subprocess.Popen(self.cmd, **kwargs) ================================================ FILE: apps/common/management/commands/services/utils.py ================================================ import threading import signal import time import daemon from daemon import pidfile from .hands import * from .hands import __version__ from .services.base import BaseService class ServicesUtil(object): def __init__(self, services, run_daemon=False, force_stop=False, stop_daemon=False): self._services = services self.run_daemon = run_daemon self.force_stop = force_stop self.stop_daemon = stop_daemon self.EXIT_EVENT = threading.Event() self.check_interval = 30 self.files_preserve_map = {} def restart(self): self.stop() time.sleep(5) self.start_and_watch() def start_and_watch(self): logging.info(time.ctime()) logging.info(f'MaxKB version {__version__}, more see https://www.maxkb.cn') self.start() if self.run_daemon: self.show_status() with self.daemon_context: self.watch() else: self.watch() def start(self): for service in self._services: service: BaseService service.start() self.files_preserve_map[service.name] = service.log_file time.sleep(1) def stop(self): for service in self._services: service: BaseService service.stop(force=self.force_stop) if self.stop_daemon: self._stop_daemon() # -- watch -- def watch(self): while not self.EXIT_EVENT.is_set(): try: _exit = self._watch() if _exit: break time.sleep(self.check_interval) except KeyboardInterrupt: print('Start stop services') break self.clean_up() def _watch(self): for service in self._services: service: BaseService service.watch() if service.EXIT_EVENT.is_set(): self.EXIT_EVENT.set() return True return False # -- end watch -- def clean_up(self): if not self.EXIT_EVENT.is_set(): self.EXIT_EVENT.set() self.stop() def show_status(self): for service in self._services: service: BaseService service.show_status() # -- daemon -- def _stop_daemon(self): if self.daemon_pid and self.daemon_is_running: os.kill(self.daemon_pid, 15) self.remove_daemon_pid() def remove_daemon_pid(self): if os.path.isfile(self.daemon_pid_filepath): os.unlink(self.daemon_pid_filepath) @property def daemon_pid(self): if not os.path.isfile(self.daemon_pid_filepath): return 0 with open(self.daemon_pid_filepath) as f: try: pid = int(f.read().strip()) except ValueError: pid = 0 return pid @property def daemon_is_running(self): try: os.kill(self.daemon_pid, 0) except (OSError, ProcessLookupError): return False else: return True @property def daemon_pid_filepath(self): return os.path.join(TMP_DIR, 'mk.pid') @property def daemon_log_filepath(self): return os.path.join(LOG_DIR, 'mk.log') @property def daemon_context(self): daemon_log_file = open(self.daemon_log_filepath, 'a') context = daemon.DaemonContext( pidfile=pidfile.TimeoutPIDLockFile(self.daemon_pid_filepath), signal_map={ signal.SIGTERM: lambda x, y: self.clean_up(), signal.SIGHUP: 'terminate', }, stdout=daemon_log_file, stderr=daemon_log_file, files_preserve=list(self.files_preserve_map.values()), detach_process=True, ) return context # -- end daemon -- ================================================ FILE: apps/common/management/commands/start.py ================================================ from .services.command import BaseActionCommand, Action class Command(BaseActionCommand): help = 'Start services' action = Action.start.value ================================================ FILE: apps/common/management/commands/status.py ================================================ from .services.command import BaseActionCommand, Action class Command(BaseActionCommand): help = 'Show services status' action = Action.status.value ================================================ FILE: apps/common/management/commands/stop.py ================================================ from .services.command import BaseActionCommand, Action class Command(BaseActionCommand): help = 'Stop services' action = Action.stop.value ================================================ FILE: apps/common/middleware/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/7/11 10:43 @desc: """ ================================================ FILE: apps/common/middleware/chat_headers_middleware.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: static_headers_middleware.py @date:2024/3/13 18:26 @desc: """ from django.utils.deprecation import MiddlewareMixin from common.cache_data.application_access_token_cache import get_application_access_token from maxkb.const import CONFIG class ChatHeadersMiddleware(MiddlewareMixin): def process_response(self, request, response): if request.path.startswith(CONFIG.get_chat_path()) and not request.path.startswith( CONFIG.get_chat_path() + '/api'): access_token = request.path.replace(CONFIG.get_chat_path() + '/', '') if access_token.__contains__('/') or access_token == 'undefined': return response application_access_token = get_application_access_token(access_token, True) if application_access_token is not None: white_active = application_access_token.get('white_active', False) white_list = application_access_token.get('white_list', []) application_icon = application_access_token.get('application_icon') application_name = application_access_token.get('application_name') if white_active: # 添加自定义的响应头 response[ 'Content-Security-Policy'] = f'frame-ancestors {" ".join(white_list)}' response.content = (response.content.decode('utf-8').replace( '', f'') .replace('MaxKB', f'{application_name}').encode( "utf-8")) return response ================================================ FILE: apps/common/middleware/cross_domain_middleware.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: cross_domain_middleware.py @date:2024/5/8 13:36 @desc: """ from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin from common.cache_data.application_api_key_cache import get_application_api_key class CrossDomainMiddleware(MiddlewareMixin): def process_request(self, request): if request.method == 'OPTIONS': return HttpResponse(status=200, headers={ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,DELETE,PUT", "Access-Control-Allow-Headers": "Origin,X-Requested-With,Content-Type,Accept,Authorization,token"}) def process_response(self, request, response): auth = request.META.get('HTTP_AUTHORIZATION') origin = request.META.get('HTTP_ORIGIN') if auth is not None and any([str(auth).startswith(prefix) for prefix in ['Bearer application-', 'Bearer agent-']]) and origin is not None: application_api_key = get_application_api_key(str(auth), True) cross_domain_list = application_api_key.get('cross_domain_list', []) allow_cross_domain = application_api_key.get('allow_cross_domain', False) if allow_cross_domain: response['Access-Control-Allow-Methods'] = 'GET,POST,DELETE,PUT' response[ 'Access-Control-Allow-Headers'] = "Origin,X-Requested-With,Content-Type,Accept,Authorization,token" if cross_domain_list is None or len(cross_domain_list) == 0: response['Access-Control-Allow-Origin'] = "*" elif cross_domain_list.__contains__(origin): response['Access-Control-Allow-Origin'] = origin return response ================================================ FILE: apps/common/middleware/doc_headers_middleware.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: static_headers_middleware.py @date:2024/3/13 18:26 @desc: """ from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin from common.auth import TokenDetails, handles from maxkb.const import CONFIG content = """ Document """.replace("/api/user/profile", CONFIG.get_admin_path() + '/api/user/profile').replace('/admin/login', CONFIG.get_admin_path() + '/login') class DocHeadersMiddleware(MiddlewareMixin): def process_response(self, request, response): if request.path.startswith(CONFIG.get_admin_path() + '/api-doc/') or request.path.startswith( CONFIG.get_chat_path() + '/api-doc/'): auth = request.COOKIES.get('Authorization') if auth is None: return HttpResponse(content) else: if not auth.startswith("Bearer "): return HttpResponse(content) try: token = auth[7:] token_details = TokenDetails(token) for handle in handles: if handle.support(request, token, token_details.get_token_details): handle.handle(request, token, token_details.get_token_details) return response return HttpResponse(content) except Exception as e: return HttpResponse(content) return response ================================================ FILE: apps/common/middleware/gzip.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: gzip.py @date:2025/2/27 10:03 @desc: """ from django.utils.cache import patch_vary_headers from django.utils.deprecation import MiddlewareMixin from django.utils.regex_helper import _lazy_re_compile from django.utils.text import compress_sequence, compress_string re_accepts_gzip = _lazy_re_compile(r"\bgzip\b") class GZipMiddleware(MiddlewareMixin): """ Compress content if the browser allows gzip compression. Set the Vary header accordingly, so that caches will base their storage on the Accept-Encoding header. """ max_random_bytes = 100 def process_response(self, request, response): if request.method != 'GET' or request.path.startswith('/api'): return response # It's not worth attempting to compress really short responses. if not response.streaming and len(response.content) < 200: return response # Avoid gzipping if we've already got a content-encoding. if response.has_header("Content-Encoding"): return response patch_vary_headers(response, ("Accept-Encoding",)) ae = request.META.get("HTTP_ACCEPT_ENCODING", "") if not re_accepts_gzip.search(ae): return response if response.streaming: if response.is_async: # pull to lexical scope to capture fixed reference in case # streaming_content is set again later. original_iterator = response.streaming_content async def gzip_wrapper(): async for chunk in original_iterator: yield compress_string( chunk, max_random_bytes=self.max_random_bytes, ) response.streaming_content = gzip_wrapper() else: response.streaming_content = compress_sequence( response.streaming_content, max_random_bytes=self.max_random_bytes, ) # Delete the `Content-Length` header for streaming content, because # we won't know the compressed size until we stream it. del response.headers["Content-Length"] else: # Return the compressed content only if it's actually shorter. compressed_content = compress_string( response.content, max_random_bytes=self.max_random_bytes, ) if len(compressed_content) >= len(response.content): return response response.content = compressed_content response.headers["Content-Length"] = str(len(response.content)) # If there is a strong ETag, make it weak to fulfill the requirements # of RFC 9110 Section 8.8.1 while also allowing conditional request # matches on ETags. etag = response.get("ETag") if etag and etag.startswith('"'): response.headers["ETag"] = "W/" + etag response.headers["Content-Encoding"] = "gzip" return response ================================================ FILE: apps/common/mixins/__init__.py ================================================ ================================================ FILE: apps/common/mixins/api_mixin.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: ApiMixin.py @date:2025/4/14 18:03 @desc: """ class APIMixin: @staticmethod def get_request(): return None @staticmethod def get_response(): return None @staticmethod def get_parameters(): """ return OpenApiParameter( # 参数的名称是done name="done", # 对参数的备注 description="是否完成", # 指定参数的类型 type=OpenApiTypes.BOOL, location=OpenApiParameter.QUERY, # 指定必须给 required=True, # 指定枚举项 enum=[True, False], ) """ return None ================================================ FILE: apps/common/mixins/app_model_mixin.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: app_model_mixin.py @date:2023/9/21 9:41 @desc: """ from django.db import models class AppModelMixin(models.Model): objects = models.Manager() create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, db_index=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, db_index=True) class Meta: abstract = True ordering = ['create_time'] ================================================ FILE: apps/common/result/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/4/14 15:45 @desc: """ from .api import * from .result import * ================================================ FILE: apps/common/result/api.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: api.py @date:2025/4/14 15:20 @desc: """ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers class DefaultResultSerializer(serializers.Serializer): """ 响应结果 """ code = serializers.IntegerField(required=True, help_text=_('response code'), label=_('response code')) message = serializers.CharField(required=False, default="success", help_text=_('error prompt'), label=_('error prompt')) data = serializers.BooleanField(required=False, default=True) class ResultSerializer(serializers.Serializer): """ 响应结果 """ code = serializers.IntegerField(required=True, help_text=_('response code'), label=_('response code')) message = serializers.CharField(required=False, default="success", help_text=_('error prompt'), label=_('error prompt')) def get_data(self): pass def __init__(self, **kwargs): self.fields['data'] = self.get_data() super().__init__(**kwargs) class PageDataResponse(serializers.Serializer): """ 分页数据 """ total = serializers.IntegerField(required=True, label=_('total number of data')) current = serializers.IntegerField(required=True, label=_('current page')) size = serializers.IntegerField(required=True, label=_('page size')) def __init__(self, records, **kwargs): self.fields['records'] = records super().__init__(**kwargs) class ResultPageSerializer(ResultSerializer): def __init__(self, **kwargs): super().__init__(**kwargs) self.fields['data'] = PageDataResponse(self.get_data()) ================================================ FILE: apps/common/result/result.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: result.py @date:2025/4/14 15:18 @desc: """ from typing import List from django.http import JsonResponse from django.utils.translation import gettext_lazy as _ from rest_framework import status class Page(dict): """ 分页对象 """ def __init__(self, total: int, records: List, current_page: int, page_size: int, **kwargs): super().__init__(**{'total': total, 'records': records, 'current': current_page, 'size': page_size}) class Result(JsonResponse): charset = 'utf-8' """ 接口统一返回对象 """ def __init__(self, code=200, message=_('Success'), data=None, response_status=status.HTTP_200_OK, **kwargs): back_info_dict = {"code": code, "message": message, 'data': data} super().__init__(data=back_info_dict, status=response_status, **kwargs) def success(data, **kwargs): """ 获取一个成功的响应对象 :param data: 接口响应数据 :return: 请求响应对象 """ return Result(data=data, **kwargs) def error(message, **kwargs): """ 获取一个失败的响应对象 :param message: 错误提示 :return: 接口响应对象 """ return Result(code=500, message=message, **kwargs) ================================================ FILE: apps/common/sql/list_embedding_text.sql ================================================ SELECT problem_paragraph_mapping."id" AS "source_id", paragraph.document_id AS document_id, paragraph."id" AS paragraph_id, problem.knowledge_id AS knowledge_id, 0 AS source_type, problem."content" AS "text", paragraph.is_active AS is_active, paragraph.chunks AS chunks FROM problem problem LEFT JOIN problem_paragraph_mapping problem_paragraph_mapping ON problem_paragraph_mapping.problem_id=problem."id" LEFT JOIN paragraph paragraph ON paragraph."id" = problem_paragraph_mapping.paragraph_id ${problem} UNION SELECT paragraph."id" AS "source_id", paragraph.document_id AS document_id, paragraph."id" AS paragraph_id, paragraph.knowledge_id AS knowledge_id, 1 AS source_type, concat_ws(E'\n',paragraph.title,paragraph."content") AS "text", paragraph.is_active AS is_active, paragraph.chunks AS chunks FROM paragraph paragraph ${paragraph} ================================================ FILE: apps/common/template/email_template_en.html ================================================
Powerful and easy-to-use enterprise level intelligent agent platform

Dear user:

${code}  This is your dynamic verification code. Please fill it in within 30 minutes. To protect the security of your account, please do not provide this verification code to anyone.


Intelligent knowledge base project team


Please do not reply to this system email

================================================ FILE: apps/common/template/email_template_zh.html ================================================
强大易用的企业级智能体平台

尊敬的用户:

${code}  为您的动态验证码,请于30分钟内填写,为保障帐户安全,请勿向任何人提供此验证码。


智能知识库项目组


此为系统邮件,请勿回复

================================================ FILE: apps/common/template/email_template_zh_Hant.html ================================================
强大易用的企業級智能體平臺

尊敬的用戶:

${code}  為您的動態驗證碼,請於30分鐘內填寫,為保障帳戶安全,請勿向任何人提供此驗證碼。


智慧知識庫專案組


此為系統郵件,請勿回覆

================================================ FILE: apps/common/utils/__init__.py ================================================ ================================================ FILE: apps/common/utils/cache_util.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: cache_util.py @date:2024/7/24 19:23 @desc: """ from django.core.cache import cache def get_data_by_default_cache(key: str, get_data, cache_instance=cache, version=None, kwargs=None): """ 获取数据, 先从缓存中获取,如果获取不到再调用get_data 获取数据 @param kwargs: get_data所需参数 @param key: key @param get_data: 获取数据函数 @param cache_instance: cache实例 @param version: 版本用于隔离 @return: """ if kwargs is None: kwargs = {} if cache_instance.has_key(key, version=version): return cache_instance.get(key, version=version) data = get_data(**kwargs) cache_instance.add(key, data, version=version) return data def set_data_by_default_cache(key: str, get_data, cache_instance=cache, version=None): data = get_data() cache_instance.set(key, data, version=version) return data def get_cache(cache_key, use_get_data: any = True, cache_instance=cache, version=None): def inner(get_data): def run(*args, **kwargs): key = cache_key(*args, **kwargs) if callable(cache_key) else cache_key is_use_get_data = use_get_data(*args, **kwargs) if callable(use_get_data) else use_get_data if is_use_get_data: if cache_instance.has_key(key, version=version): return cache_instance.get(key, version=version) data = get_data(*args, **kwargs) cache_instance.add(key, data, timeout=None, version=version) return data data = get_data(*args, **kwargs) cache_instance.set(key, data, timeout=None, version=version) return data return run return inner def del_cache(cache_key, cache_instance=cache, version=None): def inner(func): def run(*args, **kwargs): key = cache_key(*args, **kwargs) if callable(cache_key) else cache_key func(*args, **kwargs) cache_instance.delete(key, version=version) return run return inner ================================================ FILE: apps/common/utils/chat_link_code.py ================================================ """ @project: MaxKB @Author: niu @file: chat_link_code.py @date: 2026/2/9 11:31 @desc: """ from typing import Union import uuid_utils.compat as uuid class UUIDEncoder: BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" BASE62_LEN = 62 @staticmethod def encode(uuid_obj: Union[uuid.UUID, str] = None) -> str: if uuid_obj is None: uuid_obj = uuid.uuid7() elif isinstance(uuid_obj, str): uuid_obj = uuid.UUID(uuid_obj) num = int(uuid_obj.hex, 16) if num == 0: return UUIDEncoder.BASE62_ALPHABET[0] result = [] while num: num, rem = divmod(num,62) result.append(UUIDEncoder.BASE62_ALPHABET[rem]) return ''.join(reversed(result)) @staticmethod def decode(encoded: str) -> uuid.UUID: num = 0 for char in encoded: num = num * UUIDEncoder.BASE62_LEN + UUIDEncoder.BASE62_ALPHABET.index(char) return uuid.UUID(int=num) @staticmethod def decode_to_str(encoded: str) -> str: return str(UUIDEncoder.decode(encoded)) ================================================ FILE: apps/common/utils/common.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: common.py @date:2025/4/14 18:23 @desc: """ import hashlib import io import mimetypes import pickle import random import re import shutil import uuid from functools import reduce from typing import List, Dict from django.core.files.uploadedfile import InMemoryUploadedFile from django.db.models import QuerySet from django.utils.translation import gettext as _ from pydub import AudioSegment from ..database_model_manage.database_model_manage import DatabaseModelManage from ..exception.app_exception import AppApiException def password_encrypt(row_password): """ 密码 md5加密 :param row_password: 密码 :return: 加密后密码 """ md5 = hashlib.md5() # 2,实例化md5() 方法 md5.update(row_password.encode()) # 3,对字符串的字节类型加密 result = md5.hexdigest() # 4,加密 return result def group_by(list_source: List, key): """ 將數組分組 :param list_source: 需要分組的數組 :param key: 分組函數 :return: key->[] """ result = {} for e in list_source: k = key(e) array = result.get(k) if k in result else [] array.append(e) result[k] = array return result SAFE_CHAR_SET = ( [chr(i) for i in range(65, 91) if chr(i) not in {'I', 'O'}] + # 大写字母 A-H, J-N, P-Z [chr(i) for i in range(97, 123) if chr(i) not in {'i', 'l', 'o'}] + # 小写字母 a-h, j-n, p-z [str(i) for i in range(10) if str(i) not in {'0', '1', '7'}] # 数字 2-6, 8-9 ) def get_random_chars(number=4): if number <= 0: return "" return ''.join(random.choices(SAFE_CHAR_SET, k=number)) def encryption(message: str): """ 加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890 :param message: :return: """ if not message: # 处理空字符串情况 return "***************" max_pre_len = 8 max_post_len = 4 message_len = len(message) pre_len = int(message_len / 5 * 2) post_len = int(message_len / 5 * 1) pre_str = "".join([message[index] for index in range(0, max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int(pre_len))]) end_str = "".join( [message[index] for index in range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len), message_len)]) content = "***************" return pre_str + content + end_str def _remove_empty_lines(text): if not isinstance(text, str): raise AppApiException(500, _('Text-to-speech node, the text content must be of string type')) if not text: raise AppApiException(500, _('Text-to-speech node, the text content cannot be empty')) result = '\n'.join(line for line in text.split('\n') if line.strip()) return markdown_to_plain_text(result) def markdown_to_plain_text(md: str) -> str: # 移除图片 ![alt](url) text = re.sub(r'!\[.*?\]\(.*?\)', '', md) # 移除链接 [text](url) text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text) # 移除 Markdown 标题符号 (#, ##, ###) text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE) # 移除加粗 **text** 或 __text__ text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) text = re.sub(r'__(.*?)__', r'\1', text) # 移除斜体 *text* 或 _text_ text = re.sub(r'\*(.*?)\*', r'\1', text) text = re.sub(r'_(.*?)_', r'\1', text) # 移除行内代码 `code` text = re.sub(r'`(.*?)`', r'\1', text) # 移除代码块 ```code``` text = re.sub(r'```[\s\S]*?```', '', text) # 移除多余的换行符 text = re.sub(r'\n{2,}', '\n', text) # 使用正则表达式去除所有 HTML 标签 text = re.sub(r'<[^>]+>', '', text) # 先移除特定媒体标签(优先级高于通用HTML标签移除) text = re.sub(r'<(?:audio|video)(?:\s+[^>]*)?>[\s\S]*?(?:)?', '', text, flags=re.IGNORECASE) text = re.sub(r']*>', '', text) # 匹配图片标签 # 去除多余的空白字符(包括换行符、制表符等) text = re.sub(r'\s+', ' ', text) # 去除表单渲染 re.sub(r'[\s\S]*?<\/form_rander>', '', text) # 去除首尾空格 text = text.strip() return text def get_file_content(path): with open(path, "r", encoding='utf-8') as file: content = file.read() return content def sub_array(array: List, item_num=10): result = [] temp = [] for item in array: temp.append(item) if len(temp) >= item_num: result.append(temp) temp = [] if len(temp) > 0: result.append(temp) return result def bytes_to_uploaded_file(file_bytes, file_name="file.txt"): content_type, _ = mimetypes.guess_type(file_name) if content_type is None: # 如果未能识别,设置为默认的二进制文件类型 content_type = "application/octet-stream" # 创建一个内存中的字节流对象 file_stream = io.BytesIO(file_bytes) # 获取文件大小 file_size = len(file_bytes) # 创建 InMemoryUploadedFile 对象 uploaded_file = InMemoryUploadedFile( file=file_stream, field_name=None, name=file_name, content_type=content_type, size=file_size, charset=None, ) return uploaded_file def any_to_amr(any_path, amr_path): """ 把任意格式转成amr文件 """ if any_path.endswith(".amr"): shutil.copy2(any_path, amr_path) return if any_path.endswith(".sil") or any_path.endswith(".silk") or any_path.endswith(".slk"): raise NotImplementedError("Not support file type: {}".format(any_path)) audio = AudioSegment.from_file(any_path) audio = audio.set_frame_rate(8000) # only support 8000 audio.export(amr_path, format="amr") return audio.duration_seconds * 1000 def any_to_mp3(any_path, mp3_path): """ 把任意格式转成mp3文件 """ if any_path.endswith(".mp3"): shutil.copy2(any_path, mp3_path) return if any_path.endswith(".sil") or any_path.endswith(".silk") or any_path.endswith(".slk"): sil_to_wav(any_path, any_path) any_path = mp3_path audio = AudioSegment.from_file(any_path) audio = audio.set_frame_rate(16000) audio.export(mp3_path, format="mp3") def sil_to_wav(silk_path, wav_path, rate: int = 24000): """ silk 文件转 wav """ try: import pysilk except ImportError: raise AppApiException("import pysilk failed, wechaty voice message will not be supported.") wav_data = pysilk.decode_file(silk_path, to_wav=True, sample_rate=rate) with open(wav_path, "wb") as f: f.write(wav_data) def split_and_transcribe(file_path, model, max_segment_length_ms=59000, audio_format="mp3"): audio_data = AudioSegment.from_file(file_path, format=audio_format) audio_length_ms = len(audio_data) if audio_length_ms <= max_segment_length_ms: return model.speech_to_text(io.BytesIO(audio_data.export(format=audio_format).read())) full_text = [] for start_ms in range(0, audio_length_ms, max_segment_length_ms): end_ms = min(audio_length_ms, start_ms + max_segment_length_ms) segment = audio_data[start_ms:end_ms] text = model.speech_to_text(io.BytesIO(segment.export(format=audio_format).read())) if isinstance(text, str): full_text.append(text) return ' '.join(full_text) def query_params_to_single_dict(query_params: Dict): return reduce(lambda x, y: {**x, **y}, list( filter(lambda item: item is not None, [({key: value} if value is not None and len(value) > 0 else None) for key, value in query_params.items()])), {}) def valid_license(model=None, count=None, message=None): def inner(func): def run(*args, **kwargs): is_license_valid = DatabaseModelManage.get_model('license_is_valid') is_license_valid = is_license_valid() if is_license_valid() is not None else False record_count = QuerySet(model).count() if not is_license_valid and record_count >= count: error_message = message or _( 'Limit {count} exceeded, please contact us (https://fit2cloud.com/).').format( count=count) raise AppApiException(400, error_message) return func(*args, **kwargs) return run return inner def post(post_function): def inner(func): def run(*args, **kwargs): result = func(*args, **kwargs) return post_function(*result) return run return inner def parse_md_image(content: str): matches = re.finditer("!\[.*?\]\(.*?\)", content) image_list = [match.group() for match in matches] return image_list def bulk_create_in_batches(model, data, batch_size=1000): if len(data) == 0: return for i in range(0, len(data), batch_size): batch = data[i:i + batch_size] model.objects.bulk_create(batch) def get_sha256_hash(_v: str | bytes): sha256 = hashlib.sha256() if isinstance(_v, str): sha256.update(_v.encode()) else: sha256.update(_v) return sha256.hexdigest() ALLOWED_CLASSES = { ("builtins", "dict"), ('uuid', 'UUID'), ("application.serializers.application", "MKInstance"), ("tools.serializers.tool", "ToolInstance"), ("knowledge.serializers.knowledge_workflow", "KBWFInstance") } class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if (module, name) in ALLOWED_CLASSES: return super().find_class(module, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name)) def restricted_loads(s): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() def flat_map(array: List[List]): """ 将二位数组转为一维数组 :param array: 二维数组 :return: 一维数组 """ result = [] for e in array: result += e return result def parse_image(content: str): matches = re.finditer("!\[.*?\]\(\.\/oss\/(image|file)\/.*?\)", content) image_list = [match.group() for match in matches] return image_list def generate_uuid(tag: str): return str(uuid.uuid5(uuid.NAMESPACE_DNS, tag)) def filter_workspace(query_list): return [q for q in query_list if q.name != "workspace_id"] def filter_special_character(_str): """ 过滤特殊字符 """ s_list = ["\\u0000"] for t in s_list: _str = _str.replace(t, '') return _str def is_valid_uuid(uuid_string): """判断字符串是否为有效的UUID""" try: uuid_obj = uuid.UUID(uuid_string) return str(uuid_obj) == uuid_string except ValueError: return False ================================================ FILE: apps/common/utils/fork.py ================================================ import copy import re import traceback from functools import reduce from typing import List, Set from urllib.parse import urljoin, urlparse, ParseResult, urlsplit, urlunparse import html2text as ht import requests from bs4 import BeautifulSoup from common.utils.logger import maxkb_logger requests.packages.urllib3.disable_warnings() class ChildLink: def __init__(self, url, tag): self.url = url self.tag = copy.deepcopy(tag) class ForkManage: def __init__(self, base_url: str, selector_list: List[str]): self.base_url = base_url self.selector_list = selector_list def fork(self, level: int, exclude_link_url: Set[str], fork_handler): self.fork_child(ChildLink(self.base_url, None), self.selector_list, level, exclude_link_url, fork_handler) @staticmethod def fork_child(child_link: ChildLink, selector_list: List[str], level: int, exclude_link_url: Set[str], fork_handler): if level < 0: return else: child_link.url = remove_fragment(child_link.url) child_url = child_link.url[:-1] if child_link.url.endswith('/') else child_link.url if not exclude_link_url.__contains__(child_url): exclude_link_url.add(child_url) response = Fork(child_link.url, selector_list).fork() fork_handler(child_link, response) for child_link in response.child_link_list: child_url = child_link.url[:-1] if child_link.url.endswith('/') else child_link.url if not exclude_link_url.__contains__(child_url): ForkManage.fork_child(child_link, selector_list, level - 1, exclude_link_url, fork_handler) def remove_fragment(url: str) -> str: parsed_url = urlparse(url) modified_url = ParseResult(scheme=parsed_url.scheme, netloc=parsed_url.netloc, path=parsed_url.path, params=parsed_url.params, query=parsed_url.query, fragment=None) return urlunparse(modified_url) class Fork: class Response: def __init__(self, content: str, child_link_list: List[ChildLink], status, message: str): self.content = content self.child_link_list = child_link_list self.status = status self.message = message @staticmethod def success(html_content: str, child_link_list: List[ChildLink]): return Fork.Response(html_content, child_link_list, 200, '') @staticmethod def error(message: str): return Fork.Response('', [], 500, message) def __init__(self, base_fork_url: str, selector_list: List[str]): base_fork_url = remove_fragment(base_fork_url) parsed = urlparse(base_fork_url) path = parsed.path.rstrip('/') self.base_fork_url = urlunparse(( parsed.scheme, parsed.netloc, path, None, None, None # fragment )) parsed = urlsplit(base_fork_url) query = parsed.query if query is not None and len(query) > 0: self.base_fork_url = self.base_fork_url + '?' + query self.selector_list = [selector for selector in selector_list if selector is not None and len(selector) > 0] self.urlparse = urlparse(self.base_fork_url) self.base_url = ParseResult(scheme=self.urlparse.scheme, netloc=self.urlparse.netloc, path='', params='', query='', fragment='').geturl() def get_child_link_list(self, bf: BeautifulSoup): pattern = "^((?!(http:|https:|tel:/|#|mailto:|javascript:))|" + self.base_fork_url + "|/).*" link_list = bf.find_all(name='a', href=re.compile(pattern)) result = [ChildLink(link.get('href'), link) if link.get('href').startswith(self.base_url) else ChildLink( self.base_url + link.get('href'), link) for link in link_list] result = [row for row in result if row.url.startswith(self.base_fork_url)] return result def get_content_html(self, bf: BeautifulSoup): if self.selector_list is None or len(self.selector_list) == 0: return str(bf) params = reduce(lambda x, y: {**x, **y}, [{'class_': selector.replace('.', '')} if selector.startswith('.') else {'id': selector.replace("#", "")} if selector.startswith("#") else {'name': selector} for selector in self.selector_list], {}) f = bf.find_all(**params) return "\n".join([str(row) for row in f]) @staticmethod def reset_url(tag, field, base_fork_url): field_value: str = tag[field] if field_value.startswith("/"): result = urlparse(base_fork_url) result_url = ParseResult(scheme=result.scheme, netloc=result.netloc, path=field_value, params='', query='', fragment='').geturl() else: result_url = urljoin( base_fork_url + '/' + (field_value if field_value.endswith('/') else field_value + '/'), ".") result_url = result_url[:-1] if result_url.endswith('/') else result_url tag[field] = result_url def reset_beautiful_soup(self, bf: BeautifulSoup): reset_config_list = [ { 'field': 'href', }, { 'field': 'src', } ] for reset_config in reset_config_list: field = reset_config.get('field') tag_list = bf.find_all(**{field: re.compile('^(?!(http:|https:|tel:/|#|mailto:|javascript:)).*')}) for tag in tag_list: self.reset_url(tag, field, self.base_fork_url) return bf @staticmethod def get_beautiful_soup(response): encoding = response.encoding if response.encoding is not None and response.encoding != 'ISO-8859-1' else response.apparent_encoding html_content = response.content.decode(encoding) beautiful_soup = BeautifulSoup(html_content, "html.parser") meta_list = beautiful_soup.find_all('meta') charset_list = Fork.get_charset_list(meta_list) if len(charset_list) > 0: charset = charset_list[0] if charset != encoding: try: html_content = response.content.decode(charset, errors='replace') except Exception as e: maxkb_logger.error(f'{e}: {traceback.format_exc()}') return BeautifulSoup(html_content, "html.parser") return beautiful_soup @staticmethod def get_charset_list(meta_list): charset_list = [] for meta in meta_list: if meta.attrs is not None: if 'charset' in meta.attrs: charset_list.append(meta.attrs.get('charset')) elif meta.attrs.get('http-equiv', '').lower() == 'content-type' and 'content' in meta.attrs: match = re.search(r'charset=([^\s;]+)', meta.attrs['content'], re.I) if match: charset_list.append(match.group(1)) return charset_list def fork(self): try: headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36' } maxkb_logger.info(f'fork:{self.base_fork_url}') response = requests.get(self.base_fork_url, verify=False, headers=headers) if response.status_code != 200: maxkb_logger.error(f"url: {self.base_fork_url} code:{response.status_code}") return Fork.Response.error(f"url: {self.base_fork_url} code:{response.status_code}") bf = self.get_beautiful_soup(response) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') return Fork.Response.error(str(e)) bf = self.reset_beautiful_soup(bf) link_list = self.get_child_link_list(bf) content = self.get_content_html(bf) r = ht.html2text(content) return Fork.Response.success(r, link_list) def handler(base_url, response: Fork.Response): maxkb_logger.info(base_url.url, base_url.tag.text if base_url.tag else None, response.content) # ForkManage('https://bbs.fit2cloud.com/c/de/6', ['.md-content']).fork(3, set(), handler) ================================================ FILE: apps/common/utils/lock.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: lock.py @date:2023/9/11 11:45 @desc: """ from functools import wraps import uuid_utils.compat as uuid from django.core.cache import caches from django_redis import get_redis_connection memory_cache = caches['default'] class RedisLock(): def __init__(self): self.lock_value = None def try_lock(self, key: str, timeout=None): """ 获取锁 :param key: 获取锁 key :param timeout 超时时间 :return: 是否获取到锁 """ redis_client = get_redis_connection("default") if timeout is None: timeout = 3600 # 默认超时时间为3600秒 self.lock_value = str(uuid.uuid7()) return redis_client.set(key, self.lock_value, nx=True, ex=timeout) def un_lock(self, key: str): """ 解锁 :param key: 解锁 key :return: 是否解锁成功 """ redis_client = get_redis_connection("default") unlock_script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ redis_client.eval(unlock_script, 1, key, self.lock_value) def lock(lock_key, timeout=None): """ 给一个函数上锁 @param lock_key: 上锁key 字符串|函数 函数返回值为字符串 @param timeout: 超时时间 :return: 装饰器函数 当前装饰器主要限制一个key只能一个线程去调用 相同key只能阻塞等待上一个任务执行完毕 不同key不需要等待 """ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): key = lock_key(*args, **kwargs) if callable(lock_key) else lock_key rlock = RedisLock() if not rlock.try_lock(key, timeout): # 获取锁失败,可自定义异常或返回 return None try: return func(*args, **kwargs) finally: rlock.un_lock(key) return wrapper return decorator ================================================ FILE: apps/common/utils/logger.py ================================================ from datetime import datetime, timedelta from logging.handlers import TimedRotatingFileHandler import os import logging maxkb_logger = logging.getLogger('max_kb') class DailyTimedRotatingFileHandler(TimedRotatingFileHandler): def rotator(self, source, dest): """ Override the original method to rotate the log file daily.""" dest = self._get_rotate_dest_filename(source) if os.path.exists(source) and not os.path.exists(dest): # 存在多个服务进程时, 保证只有一个进程成功 rotate os.rename(source, dest) @staticmethod def _get_rotate_dest_filename(source): date_yesterday = ( datetime.now() - timedelta(days=1) ).strftime('%Y-%m-%d') path = [ os.path.dirname(source), date_yesterday, os.path.basename(source) ] filename = os.path.join(*path) os.makedirs(os.path.dirname(filename), 0o700, exist_ok=True) return filename ================================================ FILE: apps/common/utils/page_utils.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: page_utils.py @date:2024/11/21 10:32 @desc: """ from math import ceil def page(query_set, page_size, handler, is_the_task_interrupted=lambda: False): """ @param query_set: 查询query_set @param page_size: 每次查询大小 @param handler: 数据处理器 @param is_the_task_interrupted: 任务是否被中断 @return: """ query = query_set.order_by("id") count = query_set.count() for i in range(0, ceil(count / page_size)): if is_the_task_interrupted(): return offset = i * page_size paragraph_list = query.all()[offset: offset + page_size] handler(paragraph_list) def page_desc(query_set, page_size, handler, is_the_task_interrupted=lambda: False): """ @param query_set: 查询query_set @param page_size: 每次查询大小 @param handler: 数据处理器 @param is_the_task_interrupted: 任务是否被中断 @return: """ query = query_set.order_by("id") count = query_set.count() for i in sorted(range(0, ceil(count / page_size)), reverse=True): if is_the_task_interrupted(): return offset = i * page_size paragraph_list = query.all()[offset: offset + page_size] handler(paragraph_list) ================================================ FILE: apps/common/utils/rsa_util.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: rsa_util.py @date:2023/11/3 11:13 @desc: """ import base64 import threading from functools import lru_cache from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher from Crypto.PublicKey import RSA from django.core import cache from django.db.models import QuerySet from common.constants.cache_version import Cache_Version from system_manage.models import SystemSetting, SettingType lock = threading.Lock() rsa_cache = cache.cache cache_key = "rsa_key" # 对密钥加密的密码 secret_code = "mac_kb_password" def generate(): """ 生成 私钥秘钥对 :return:{key:'公钥',value:'私钥'} """ # 生成一个 2048 位的密钥 key = RSA.generate(2048) # 获取私钥 encrypted_key = key.export_key(passphrase=secret_code, pkcs=8, protection="scryptAndAES128-CBC") return {'key': key.publickey().export_key(), 'value': encrypted_key} def get_key_pair(): rsa_value = rsa_cache.get(cache_key) if rsa_value is None: with lock: rsa_value = rsa_cache.get(cache_key) if rsa_value is not None: return rsa_value rsa_value = get_key_pair_by_sql() version, get_key = Cache_Version.SYSTEM.value rsa_cache.set(get_key(key='rsa_key'), rsa_value, timeout=None, version=version) return rsa_value def get_key_pair_by_sql(): system_setting = QuerySet(SystemSetting).filter(type=SettingType.RSA.value).first() if system_setting is None: kv = generate() system_setting = SystemSetting(type=SettingType.RSA.value, meta={'key': kv.get('key').decode(), 'value': kv.get('value').decode()}) system_setting.save() return system_setting.meta def encrypt(msg, public_key: str | None = None): """ 加密 :param msg: 加密数据 :param public_key: 公钥 :return: 加密后的数据 """ if public_key is None: public_key = get_key_pair().get('key') cipher = _get_encrypt_cipher(public_key) encrypt_msg = cipher.encrypt(msg.encode("utf-8")) return base64.b64encode(encrypt_msg).decode() def decrypt(msg, pri_key: str | None = None): """ 解密 :param msg: 需要解密的数据 :param pri_key: 私钥 :return: 解密后数据 """ if pri_key is None: pri_key = get_key_pair().get('value') cipher = _get_cipher(pri_key) decrypt_data = cipher.decrypt(base64.b64decode(msg), 0) return decrypt_data.decode("utf-8") @lru_cache(maxsize=2) def _get_encrypt_cipher(public_key: str): """缓存加密 cipher 对象""" return PKCS1_cipher.new(RSA.importKey(extern_key=public_key, passphrase=secret_code)) def rsa_long_encrypt(message, public_key: str | None = None, length=200): """ 超长文本加密 :param message: 需要加密的字符串 :param public_key 公钥 :param length: 1024bit的证书用100, 2048bit的证书用 200 :return: 加密后的数据 """ if public_key is None: public_key = get_key_pair().get('key') cipher = _get_encrypt_cipher(public_key) if len(message) <= length: result = base64.b64encode(cipher.encrypt(message.encode('utf-8'))) else: rsa_text = [] for i in range(0, len(message), length): cont = message[i:i + length] rsa_text.append(cipher.encrypt(cont.encode('utf-8'))) cipher_text = b''.join(rsa_text) result = base64.b64encode(cipher_text) return result.decode() @lru_cache(maxsize=2) def _get_cipher(pri_key: str): """缓存 cipher 对象,避免重复创建""" return PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code)) def rsa_long_decrypt(message, pri_key: str | None = None, length=256): """ 超长文本解密,优化内存使用 :param message: 需要解密的数据 :param pri_key: 秘钥 :param length : 1024bit的证书用128,2048bit证书用256位 :return: 解密后的数据 """ if pri_key is None: pri_key = get_key_pair().get('value') cipher = _get_cipher(pri_key) base64_de = base64.b64decode(message) # 使用 bytearray 减少内存分配 result = bytearray() for i in range(0, len(base64_de), length): result.extend(cipher.decrypt(base64_de[i:i + length], 0)) return result.decode() ================================================ FILE: apps/common/utils/shared_resource_auth.py ================================================ """ @project: MaxKB-xpack-ee @Author: niu @file: shared_resource_auth.py @date: 2026/3/11 11:22 @desc: """ from typing import List from django.db.models import QuerySet from common.database_model_manage.database_model_manage import DatabaseModelManage from knowledge.models import Knowledge from tools.models import Tool def filter_authorized_ids(resource_type: str, ids: List[str], workspace_id: str) -> List[str]: """ 通用授权过滤函数 @param resource_type: 资源类型 ('model', 'tool', 'knowledge') @param ids: 待过滤的ID列表 @param workspace_id: 工作空间ID @return: 授权通过的ID列表 """ if not ids: return [] auth_func = DatabaseModelManage.get_model(f"get_authorized_{resource_type}") model_class = {'tool': Tool, 'knowledge': Knowledge}.get(resource_type) if model_class is None: return ids same_workspace_ids = list( QuerySet(model_class).filter(id__in=ids, workspace_id=workspace_id) .values_list('id', flat=True) ) cross_workspace_ids = [i for i in ids if i not in set(map(str, same_workspace_ids))] authorized_ids = set(map(str, same_workspace_ids)) if cross_workspace_ids and auth_func is not None: cross_queryset = QuerySet(model_class).filter(id__in=cross_workspace_ids) authorized = auth_func(cross_queryset, workspace_id) authorized_ids.update(str(r.id) for r in authorized) return [i for i in ids if i in authorized_ids] ================================================ FILE: apps/common/utils/split_model.py ================================================ # coding=utf-8 """ @project: qabot @Author:虎 @file: split_model.py @date:2023/9/1 15:12 @desc: """ import re from functools import reduce from typing import List, Dict import jieba def get_level_block(text, level_content_list, level_content_index, cursor): """ 从文本中获取块数据 :param text: 文本 :param level_content_list: 拆分的title数组 :param level_content_index: 指定的下标 :param cursor: 开始的下标位置 :return: 拆分后的文本数据 """ start_content: str = level_content_list[level_content_index].get('content') next_content = level_content_list[level_content_index + 1].get("content") if level_content_index + 1 < len( level_content_list) else None start_index = text.index(start_content, cursor) end_index = text.index(next_content, start_index + 1) if next_content is not None else len(text) return text[start_index + len(start_content):end_index], end_index def to_tree_obj(content, state='title'): """ 转换为树形对象 :param content: 文本数据 :param state: 状态: title block :return: 转换后的数据 """ return {'content': content, 'state': state} def remove_special_symbol(str_source: str): """ 删除特殊字符 :param str_source: 需要删除的文本数据 :return: 删除后的数据 """ return str_source def filter_special_symbol(content: dict): """ 过滤文本中的特殊字符 :param content: 需要过滤的对象 :return: 过滤后返回 """ content['content'] = remove_special_symbol(content['content']) return content def flat(tree_data_list: List[dict], parent_chain: List[dict], result: List[dict]): """ 扁平化树形结构数据 :param tree_data_list: 树形接口数据 :param parent_chain: 父级数据 传[] 用于递归存储数据 :param result: 响应数据 传[] 用于递归存放数据 :return: result 扁平化后的数据 """ if parent_chain is None: parent_chain = [] if result is None: result = [] for tree_data in tree_data_list: p = parent_chain.copy() p.append(tree_data) result.append(to_flat_obj(parent_chain, content=tree_data["content"], state=tree_data["state"])) children = tree_data.get('children') if children is not None and len(children) > 0: flat(children, p, result) return result def to_paragraph(obj: dict): """ 转换为段落 :param obj: 需要转换的对象 :return: 段落对象 """ content = obj['content'] return {"keywords": get_keyword(content), 'parent_chain': list(map(lambda p: p['content'], obj['parent_chain'])), 'content': ",".join(list(map(lambda p: p['content'], obj['parent_chain']))) + content} def get_keyword(content: str): """ 获取content中的关键词 :param content: 文本 :return: 关键词数组 """ stopwords = [':', '“', '!', '”', '\n', '\\s'] cutworms = jieba.lcut(content) return list(set(list(filter(lambda k: (k not in stopwords) | len(k) > 1, cutworms)))) def titles_to_paragraph(list_title: List[dict]): """ 将同一父级的title转换为块段落 :param list_title: 同父级title :return: 块段落 """ if len(list_title) > 0: content = "\n,".join( list(map(lambda d: d['content'].strip("\r\n").strip("\n").strip("\\s"), list_title))) return {'keywords': '', 'parent_chain': list( map(lambda p: p['content'].strip("\r\n").strip("\n").strip("\\s"), list_title[0]['parent_chain'])), 'content': ",".join(list( map(lambda p: p['content'].strip("\r\n").strip("\n").strip("\\s"), list_title[0]['parent_chain']))) + content} return None def parse_group_key(level_list: List[dict]): """ 将同级别同父级的title生成段落,加上本身的段落数据形成新的数据 :param level_list: title n 级数据 :return: 根据title生成的数据 + 段落数据 """ result = [] group_data = group_by(list(filter(lambda f: f['state'] == 'title' and len(f['parent_chain']) > 0, level_list)), key=lambda d: ",".join(list(map(lambda p: p['content'], d['parent_chain'])))) result += list(map(lambda group_data_key: titles_to_paragraph(group_data[group_data_key]), group_data)) result += list(map(to_paragraph, list(filter(lambda f: f['state'] == 'block', level_list)))) return result def to_block_paragraph(tree_data_list: List[dict]): """ 转换为块段落对象 :param tree_data_list: 树数据 :return: 块段落 """ flat_list = flat(tree_data_list, [], []) level_group_dict: dict = group_by(flat_list, key=lambda f: f['level']) return list(map(lambda level: parse_group_key(level_group_dict[level]), level_group_dict)) def parse_title_level(text, content_level_pattern: List, index): if index >= len(content_level_pattern): return [] result = parse_level(text, content_level_pattern[index]) if len(result) == 0 and len(content_level_pattern) > index: return parse_title_level(text, content_level_pattern, index + 1) return result def parse_level(text, pattern: str): """ 获取正则匹配到的文本 :param text: 需要匹配的文本 :param pattern: 正则 :return: 符合正则的文本 """ level_content_list = list(map(to_tree_obj, [r[0:255] for r in re_findall(pattern, text) if r is not None])) # 过滤掉空标题或只包含#和空白字符的标题 filtered_list = [item for item in level_content_list if item['content'].strip(' ') and item['content'].replace('#', '').strip(' ')] return list(map(filter_special_symbol, filtered_list)) def re_findall(pattern, text): # 检查 pattern 是否为空或无效 if pattern is None: return [] # 如果是字符串类型,检查是否为空字符串 if isinstance(pattern, str) and (not pattern or not pattern.strip()): return [] try: result = re.findall(pattern, text, flags=0) except re.error: return [] return list(filter(lambda r: r is not None and len(r) > 0, reduce(lambda x, y: [*x, *y], list( map(lambda row: [*(row if isinstance(row, tuple) else [row])], result)), []))) def to_flat_obj(parent_chain: List[dict], content: str, state: str): """ 将树形属性转换为扁平对象 :param parent_chain: :param content: :param state: :return: """ return {'parent_chain': parent_chain, 'level': len(parent_chain), "content": content, 'state': state} def flat_map(array: List[List]): """ 将二位数组转为一维数组 :param array: 二维数组 :return: 一维数组 """ result = [] for e in array: result += e return result def group_by(list_source: List, key): """ 將數組分組 :param list_source: 需要分組的數組 :param key: 分組函數 :return: key->[] """ result = {} for e in list_source: k = key(e) array = result.get(k) if k in result else [] array.append(e) result[k] = array return result def result_tree_to_paragraph(result_tree: List[dict], result, parent_chain, with_filter: bool): """ 转换为分段对象 :param result_tree: 解析文本的树 :param result: 传[] 用于递归 :param parent_chain: 传[] 用户递归存储数据 :param with_filter: 是否过滤block :return: List[{'problem':'xx','content':'xx'}] """ for item in result_tree: if item.get('state') == 'block': result.append({'title': " ".join(parent_chain), 'content': filter_special_char(item.get("content")) if with_filter else item.get("content")}) children = item.get("children") if children is not None and len(children) > 0: result_tree_to_paragraph(children, result, [*parent_chain, remove_special_symbol(item.get('content'))], with_filter) return result def post_handler_paragraph(content: str, limit: int): """ 根据文本的最大字符分段 :param content: 需要分段的文本字段 :param limit: 最大分段字符 :return: 分段后数据 """ result = [] temp_char, start = '', 0 while (pos := content.find("\n", start)) != -1: split, start = content[start:pos + 1], pos + 1 if len(temp_char + split) > limit: if len(temp_char) > 4096: pass result.append(temp_char) temp_char = '' temp_char = temp_char + split temp_char = temp_char + content[start:] if len(temp_char) > 0: if len(temp_char) > 4096: pass result.append(temp_char) pattern = "[\\S\\s]{1," + str(limit) + '}' # 如果\n 单段超过限制,则继续拆分 return reduce(lambda x, y: [*x, *y], map(lambda row: re.findall(pattern, row), result), []) def smart_split_paragraph(content: str, limit: int): """ 智能分段:在limit前找到合适的分割点(句号、回车等) :param content: 需要分段的文本 :param limit: 最大字符限制 :return: 分段后的文本列表 """ if len(content) <= limit: return [content] result = [] start = 0 while start < len(content): end = start + limit if end >= len(content): # 剩余文本不超过限制,直接添加 result.append(content[start:]) break # 在limit范围内寻找最佳分割点 best_split = end # 优先级:句号 > 感叹号/问号 > 回车 split_chars = [ ('。', 0), ('.', 0), # 中英文句号 ('!', 0), ('!', 0), # 中英文感叹号 ('?', 0), ('?', 0), # 中英文问号 ] # 从后往前找分割点 for i in range(end - 1, start + limit // 2, -1): # 至少保留一半内容 for char, offset in split_chars: if content[i] == char: best_split = i + 1 # 包含分隔符在当前段 break if best_split != end: break # 如果找不到合适分割点,使用原始limit if best_split == end and end < len(content): best_split = end result.append(content[start:best_split]) start = best_split return [text for text in result if text.strip()] replace_map = { re.compile('\n+'): '\n', re.compile(' +'): ' ', re.compile('#+'): "", re.compile("\t+"): '' } def filter_special_char(content: str): """ 过滤特殊字段 :param content: 文本 :return: 过滤后字段 """ items = replace_map.items() for key, value in items: content = re.sub(key, value, content) return content class SplitModel: def __init__(self, content_level_pattern, with_filter=True, limit=100000): self.content_level_pattern = content_level_pattern self.with_filter = with_filter if type(limit) is not int: limit = int(limit) if limit is None or limit > 100000: limit = 100000 if limit < 50: limit = 50 self.limit = limit def parse_to_tree(self, text: str, index=0): """ 解析文本 :param text: 需要解析的文本 :param index: 从那个正则开始解析 :return: 解析后的树形结果数据 """ level_content_list = parse_title_level(text, self.content_level_pattern, index) if len(level_content_list) == 0: return [to_tree_obj(row, 'block') for row in smart_split_paragraph(text, limit=self.limit)] if index == 0 and text.lstrip().index(level_content_list[0]["content"].lstrip()) != 0: level_content_list.insert(0, to_tree_obj("")) cursor = 0 level_title_content_list = [item for item in level_content_list if item.get('state') == 'title'] for i in range(len(level_title_content_list)): start_content: str = level_title_content_list[i].get('content') if cursor < text.index(start_content, cursor): for row in smart_split_paragraph(text[cursor: text.index(start_content, cursor)], limit=self.limit): level_content_list.insert(0, to_tree_obj(row, 'block')) block, cursor = get_level_block(text, level_title_content_list, i, cursor) if len(block) == 0: continue children = self.parse_to_tree(text=block, index=index + 1) level_title_content_list[i]['children'] = children first_child_idx_in_block = block.lstrip().index(children[0]["content"].lstrip()) if first_child_idx_in_block != 0: inner_children = self.parse_to_tree(block[:first_child_idx_in_block], index + 1) level_title_content_list[i]['children'].extend(inner_children) return level_content_list def parse(self, text: str): """ 解析文本 :param text: 文本数据 :return: 解析后数据 {content:段落数据,keywords:[‘段落关键词’],parent_chain:['段落父级链路']} """ text = text.replace('\r\n', '\n') text = text.replace('\r', '\n') text = text.replace("\0", '') result_tree = self.parse_to_tree(text, 0) result = result_tree_to_paragraph(result_tree, [], [], self.with_filter) for e in result: if len(e['content']) > 4096: pass title_list = list(set([row.get('title') for row in result])) return [item for item in [self.post_reset_paragraph(row, title_list) for row in result] if 'content' in item and len(item.get('content').strip()) > 0] def post_reset_paragraph(self, paragraph: Dict, title_list: List[str]): result = self.content_is_null(paragraph, title_list) result = self.filter_title_special_characters(result) result = self.sub_title(result) return result @staticmethod def sub_title(paragraph: Dict): if 'title' in paragraph: title = paragraph.get('title') if len(title) > 255: return {**paragraph, 'title': title[0:255], 'content': title[255:len(title)] + paragraph.get('content')} return paragraph @staticmethod def content_is_null(paragraph: Dict, title_list: List[str]): if 'title' in paragraph: title = paragraph.get('title') content = paragraph.get('content') if (content is None or len(content.strip()) == 0) and (title is not None and len(title) > 0): find = [t for t in title_list if t.__contains__(title) and t != title] if find: return {'title': '', 'content': ''} return {'title': '', 'content': title} return paragraph @staticmethod def filter_title_special_characters(paragraph: Dict): title = paragraph.get('title') if 'title' in paragraph else '' for title_special_characters in title_special_characters_list: title = title.replace(title_special_characters, '') return {**paragraph, 'title': title} title_special_characters_list = ['#', '\n', '\r', '\\s'] default_split_pattern = { 'md': [re.compile('(?<=^)# .*|(?<=\\n)# .*'), re.compile('(?<=\\n)(? 0: for i in range(len(arg_names) - num_defaults, len(arg_names)): arg_name = arg_names[i] if arg_name in params: default_value = params[arg_name] if isinstance(default_value, str): defaults.append(ast.Constant(value=default_value)) elif isinstance(default_value, (int, float, bool)): defaults.append(ast.Constant(value=default_value)) elif default_value is None: defaults.append(ast.Constant(value=None)) else: defaults.append(ast.Constant(value=str(default_value))) else: # 如果某个参数没有默认值,需要添加 None 占位 defaults.append(ast.Constant(value=None)) node.args.defaults = defaults # 修改返回类型注解为 Result node.returns = ast.Name(id='Result', ctx=ast.Load()) # 修改 return 语句为 return Result(result=..., tool_id=...) class ReturnTransformer(ast.NodeTransformer): def __init__(self, func_name): self.func_name = func_name def visit_Return(self, node): if node.value is None: # return 语句没有返回值 new_return = ast.Return( value=ast.Call( func=ast.Name(id='Result', ctx=ast.Load()), args=[], keywords=[ ast.keyword(arg='result', value=ast.Constant(value=None)), ast.keyword(arg='tool_id', value=ast.Constant(value=tool_id)) ] ) ) else: # return 语句有返回值 new_return = ast.Return( value=ast.Call( func=ast.Name(id='Result', ctx=ast.Load()), args=[], keywords=[ ast.keyword(arg='result', value=node.value), ast.keyword(arg='tool_id', value=ast.Constant(value=tool_id)) ] ) ) return ast.copy_location(new_return, node) transformer = ReturnTransformer(node.name) node = transformer.visit(node) ast.fix_missing_locations(node) func_code = ast.unparse(node) # 有些模型不支持name是中文,例如: deepseek, 其他模型未知 escaped_desc = (name + ' ' + description).replace('\n', ' ').replace("'", " ") functions.append(f"@mcp.tool(description='{escaped_desc}')\n{func_code}\n") else: other_code.append(ast.unparse(node)) # 构建完整的 MCP 服务器代码 code_parts = ["from mcp.server.fastmcp import FastMCP"] code_parts.extend(imports) code_parts.append(f"\nfrom pydantic import BaseModel") code_parts.append(f"\nfrom typing import Any") code_parts.append(f"\nclass Result(BaseModel):") code_parts.append(f"\n\tresult: Any") code_parts.append(f"\n\ttool_id: str\n") code_parts.append(f"\nmcp = FastMCP(\"{uuid.uuid7()}\")\n") code_parts.extend(other_code) code_parts.extend(functions) code_parts.append("\nmcp.run(transport=\"stdio\")\n") return "\n".join(code_parts) def generate_mcp_server_code(self, code_str, params, name, description, tool_id): code = self._generate_mcp_server_code(code_str, params, name, description, tool_id) set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else '' return f""" import os, sys, logging logging.basicConfig(level=logging.WARNING) logging.getLogger("mcp").setLevel(logging.ERROR) logging.getLogger("mcp.server").setLevel(logging.ERROR) path_to_exclude = ['/opt/py3/lib/python3.11/site-packages', '/opt/maxkb-app/apps'] sys.path = [p for p in sys.path if p not in path_to_exclude] sys.path += {_sandbox_python_sys_path} {set_run_user} os.environ.clear() exec({dedent(code)!a}) """ def get_tool_mcp_config(self, tool, params): _code = self.generate_mcp_server_code(tool.code, params, tool.name, tool.desc, str(tool.id)) maxkb_logger.debug(f"Python code of mcp tool: {_code}") compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode() tool_config = { 'command': sys.executable, 'args': [ '-c', f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())', ], 'cwd': _sandbox_path, 'env': { 'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so', }, 'transport': 'stdio', } return tool_config def get_app_mcp_config(self, api_key): app_config = { 'url': f'http://127.0.0.1:8080{CONFIG.get_chat_path()}/api/mcp', 'transport': 'streamable_http', 'headers': { 'Authorization': f'Bearer {api_key}', }, } return app_config def _exec(self, execute_file): kwargs = {'cwd': BASE_DIR, 'env': { 'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so', }} def _set_resource_limit(): if not _enable_sandbox or not sys.platform.startswith("linux"): return with suppress(Exception): resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2) with suppress(Exception): os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores))) try: subprocess_result = subprocess.run( [sys.executable, execute_file], timeout=_process_limit_timeout_seconds, text=True, capture_output=True, **kwargs, preexec_fn=_set_resource_limit ) return subprocess_result except subprocess.TimeoutExpired: raise Exception(_(f"Process execution timed out after {_process_limit_timeout_seconds} seconds.")) def validate_mcp_transport(self, code_str): servers = json.loads(code_str) for server, config in servers.items(): if config.get('transport') not in ['sse', 'streamable_http']: raise Exception(_('Only support transport=sse or transport=streamable_http')) @contextmanager def execution_timer(id=""): start = time.perf_counter() try: yield finally: maxkb_logger.debug(f"Tool execution({id}) takes {time.perf_counter() - start:.6f} seconds.") ================================================ FILE: apps/common/utils/ts_vecto_util.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: ts_vecto_util.py @date:2024/4/16 15:26 @desc: """ import re import uuid_utils.compat as uuid from typing import List import jieba import jieba.posseg jieba_word_list_cache = [chr(item) for item in range(38, 84)] for jieba_word in jieba_word_list_cache: jieba.add_word('#' + jieba_word + '#') # r"(?i)\b(?:https?|ftp|tcp|file)://[^\s]+\b", # 某些不分词数据 # r'"([^"]*)"' word_pattern_list = [r"v\d+.\d+.\d+", r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}"] remove_chars = '\n , :\'<>!@#¥%……&*()!@#$%^&*(): ;,/"./' jieba_remove_flag_list = ['x', 'w'] def get_word_list(text: str): result = [] for pattern in word_pattern_list: word_list = re.findall(pattern, text) for child_list in word_list: for word in child_list if isinstance(child_list, tuple) else [child_list]: # 不能有: 所以再使用: 进行分割 if word.__contains__(':'): item_list = word.split(":") for w in item_list: result.append(w) else: result.append(word) return result def replace_word(word_dict, text: str): for key in word_dict: pattern = '(? FOLDER_DEPTH: raise serializers.ValidationError(_('Folder depth cannot exceed 10000 levels')) def get_max_depth(current_node): if not current_node: return 0 # 获取所有后代节点 descendants = current_node.get_descendants() if not descendants.exists(): return 0 # 获取最大深度 max_level = descendants.order_by('-level').first().level current_level = current_node.level max_depth = max_level - current_level return max_depth def has_target_permission(workspace_id, source, user_id, target): return QuerySet(WorkspaceUserResourcePermission).filter(workspace_id=workspace_id, user_id=user_id, auth_target_type=source, target=target, permission_list__contains=['MANAGE']).exists() class FolderSerializer(serializers.Serializer): id = serializers.CharField(required=True, label=_('folder id')) name = serializers.CharField(required=True, label=_('folder name')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('folder description')) user_id = serializers.CharField(required=True, label=_('folder user id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) parent_id = serializers.CharField(required=False, label=_('parent id')) class Create(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_('user id')) source = serializers.CharField(required=True, label=_('source')) def insert(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) FolderCreateRequest(data=instance).is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') if not workspace_id: workspace_id = 'default' parent_id = instance.get('parent_id') if not parent_id: parent_id = workspace_id name = instance.get('name') Folder = get_folder_type(self.data.get('source')) # noqa if QuerySet(Folder).filter(name=name, workspace_id=workspace_id, parent_id=parent_id).exists(): raise serializers.ValidationError(_('Folder name already exists')) # Folder 不能超过3层 check_depth(self.data.get('source'), parent_id, workspace_id) folder = Folder( id=uuid.uuid7(), name=instance.get('name'), desc=instance.get('desc'), user_id=self.data.get('user_id'), workspace_id=workspace_id, parent_id=parent_id ) folder.save() UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': self.data.get('source') }).auth_resource(str(folder.id), is_folder=True) return FolderSerializer(folder).data class Operate(serializers.Serializer): id = serializers.CharField(required=True, label=_('folder id')) workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id')) source = serializers.CharField(required=True, label=_('source')) user_id = serializers.UUIDField(required=True, label=_('user id')) @transaction.atomic def edit(self, instance): self.is_valid(raise_exception=True) Folder = get_folder_type(self.data.get('source')) # noqa current_id = self.data.get('id') current_node = Folder.objects.get(id=current_id) if current_node is None: raise serializers.ValidationError(_('Folder does not exist')) # 模块间的移动 parent_id = instance.get('parent_id') if parent_id is None: parent_id = current_node.parent_id # 如果要修改文件夹名称,检查同级目录下是否存在同名文件夹 new_name = instance.get('name') if new_name is not None and new_name != current_node.name: if QuerySet(Folder).filter( name=new_name, parent_id=parent_id, workspace_id=current_node.workspace_id ).exclude(id=current_id).exists(): raise serializers.ValidationError(_('Folder name already exists')) edit_field_list = ['name', 'desc'] edit_dict = {field: instance.get(field) for field in edit_field_list if ( field in instance and instance.get(field) is not None)} QuerySet(Folder).filter(id=current_id).update(**edit_dict) current_node.refresh_from_db() if parent_id is not None and current_id != current_node.workspace_id and current_node.parent_id != parent_id: source_type = self.data.get('source') if has_target_permission(current_node.workspace_id, source_type, self.data.get('user_id'), parent_id) or is_workspace_manage(self.data.get('user_id'), current_node.workspace_id): current_depth = get_max_depth(current_node) check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth) parent = Folder.objects.get(id=parent_id) if QuerySet(Folder).filter(name=current_node.name, parent_id=parent_id, workspace_id=current_node.workspace_id).exists(): raise serializers.ValidationError(_('Folder name already exists')) current_node.parent = parent current_node.save() current_node.refresh_from_db() else: raise AppApiException(403, _('No permission for the target folder')) return self.one() def one(self): self.is_valid(raise_exception=True) Folder = get_folder_type(self.data.get('source')) # noqa folder = QuerySet(Folder).filter(id=self.data.get('id')).first() return FolderSerializer(folder).data @transaction.atomic def delete(self): self.is_valid(raise_exception=True) Folder = get_folder_type(self.data.get('source')) # noqa Source = get_source_type(self.data.get('source')) # noqa folder = Folder.objects.filter(id=self.data.get('id')).first() if not folder: raise serializers.ValidationError(_('Folder does not exist')) if folder.id == folder.workspace_id: raise serializers.ValidationError(_('Cannot delete root folder')) # 工作空间管理员可以删除 workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) if workspace_manage: nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True) for node in nodes: # print(node) # 删除相关的资源 self.delete_source(node) # 删除节点 node.delete() # 普通用户删除的文件夹内全部都得是自己有权限的资源 else: nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True) for node in nodes: # 删除相关的资源 source_ids = (Source.objects.filter(folder_id=node.id) .annotate(id_str=Cast('id', TextField())) .values_list('id_str', flat=True)) # 检查文件夹是否存在未授权当前用户的资源 auth_list = QuerySet(WorkspaceUserResourcePermission).filter( Q(workspace_id=self.data.get('workspace_id')) & Q(user_id=self.data.get('user_id')) & Q(auth_target_type=self.data.get('source')) & Q(target__in=source_ids) & Q(permission_list__overlap=[ResourcePermission.MANAGE, ResourcePermissionRole.ROLE]) ).count() if auth_list != len(source_ids): raise AppApiException(500, _('This folder contains resources that you dont have permission')) self.delete_source(node) node.delete() def delete_source(self, node): Source = get_source_type(self.data.get('source')) # noqa source_ids = Source.objects.filter(folder_id=node.id).values_list('id', flat=True) source = self.data.get('source') for source_id in source_ids: if source == Group.TOOL.name: ToolSerializer.Operate(data={ 'workspace_id': self.data.get('workspace_id'), 'id': source_id, }).delete() elif source == Group.APPLICATION.name: ApplicationOperateSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'application_id': source_id, 'user_id': self.data.get('user_id'), }).delete() elif source == Group.KNOWLEDGE.name: KnowledgeSerializer.Operate(data={ 'workspace_id': self.data.get('workspace_id'), 'knowledge_id': source_id, 'user_id': self.data.get('user_id'), }).delete() class FolderTreeSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id')) source = serializers.CharField(required=True, label=_('source')) @staticmethod def _check_tree_integrity(queryset): """检查树结构完整性""" for folder in queryset: if folder.lft >= folder.rght: return True # 需要重建 if folder.is_leaf_node() and folder.get_children().exists(): return True # 需要重建 return False @staticmethod def _having_read_permission_by_role(user_id: str, workspace_id: str, source: str): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None if is_x_pack_ee: return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter( Q(role__rolepermission__permission_id=f"{source}_FOLDER:READ") | Q(role__internal=True), workspace_id=workspace_id, user_id=user_id, role__type=RoleConstants.USER.value.__str__(), ).exists() return False def get_folder_tree(self, current_user, name=None): self.is_valid(raise_exception=True) user_id = current_user.id workspace_id = self.data.get('workspace_id') source = self.data.get('source') Folder = get_folder_type(source) # noqa # 检查特定工作空间的树结构完整性 workspace_folders = Folder.objects.filter(workspace_id=workspace_id) # 如果发现数据不一致,重建整个表(这是 MPTT 的限制) if self._check_tree_integrity(workspace_folders): Folder.objects.rebuild() workspace_manage = is_workspace_manage(user_id, workspace_id) base_q = Q(workspace_id=workspace_id) if name is not None: base_q &= Q(name__contains=name) if not workspace_manage: having_read_permission_by_role = has_exact_permission_by_role(user_id, workspace_id, f"{source}_FOLDER:READ", RoleConstants.USER.value.__str__()) permission_condition = ['VIEW'] if having_read_permission_by_role: permission_condition = ['VIEW', 'ROLE'] base_q &= (Q(id__in=WorkspaceUserResourcePermission.objects.filter(user_id=current_user.id, auth_target_type=self.data.get('source'), workspace_id=self.data.get( 'workspace_id'), permission_list__overlap=permission_condition) .values_list( 'target', flat=True)) | Q(id=self.data.get('workspace_id'))) nodes = Folder.objects.filter(base_q).get_cached_trees() TreeSerializer = get_folder_tree_serializer(self.data.get('source')) # noqa serializer = TreeSerializer(nodes, many=True) return [d for d in serializer.data if d.get('id') == d.get('workspace_id')] if name is None else serializer.data # 这是可序列化的字典 ================================================ FILE: apps/folders/urls.py ================================================ from django.urls import path from . import views app_name = "folder" urlpatterns = [ path('workspace///folder', views.FolderView.as_view()), path('workspace///folder/', views.FolderView.Operate.as_view()), ] ================================================ FILE: apps/folders/views/__init__.py ================================================ from .folder import * ================================================ FILE: apps/folders/views/folder.py ================================================ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ViewPermission, \ PermissionConstants, CompareConstants from common.log.log import log from common.result import result from folders.api.folder import FolderCreateAPI, FolderEditAPI, FolderReadAPI, FolderTreeReadAPI, FolderDeleteAPI from folders.serializers.folder import FolderSerializer, FolderTreeSerializer, get_folder_type def get_folder_operation_object(folder_id, source): Folder = get_folder_type(source) folder_model = QuerySet(model=Folder).filter(id=folder_id).first() if folder_model is not None: return { 'name': folder_model.name } return {} class FolderView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create folder'), summary=_('Create folder'), operation_id=_('Create folder'), # type: ignore parameters=FolderCreateAPI.get_parameters(), request=FolderCreateAPI.get_request(), responses=FolderCreateAPI.get_response(), tags=[_('Folder')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.CREATE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}"), lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.CREATE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()], [Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}" )], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role() ) @log( menu='folder', operate='Create folder', get_operation_object=lambda r, k: {'name': r.data.get('name')}, ) def post(self, request: Request, workspace_id: str, source: str): return result.success(FolderSerializer.Create( data={'user_id': request.user.id, 'source': source, 'workspace_id': workspace_id} ).insert(request.data)) @extend_schema( methods=['GET'], description=_('Get folder tree'), summary=_('Get folder tree'), operation_id=_('Get folder tree'), # type: ignore parameters=FolderTreeReadAPI.get_parameters(), responses=FolderTreeReadAPI.get_response(), tags=[_('Folder')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_WORKSPACE_USER_RESOURCE_PERMISSION"), operate=Operate.READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}"), lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}"), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN ) def get(self, request: Request, workspace_id: str, source: str): return result.success(FolderTreeSerializer( data={'workspace_id': workspace_id, 'source': source} ).get_folder_tree(request.user, request.query_params.get('name'))) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Update folder'), summary=_('Update folder'), operation_id=_('Update folder'), # type: ignore parameters=FolderEditAPI.get_parameters(), request=FolderEditAPI.get_request(), responses=FolderEditAPI.get_response(), tags=[_('Folder')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}" ), lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()], [Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}" )], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role() ) @log( menu='folder', operate='Edit folder', get_operation_object=lambda r, k: get_folder_operation_object(k.get('folder_id'), k.get('source')), ) def put(self, request: Request, workspace_id: str, source: str, folder_id: str): return result.success(FolderSerializer.Operate( data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id} ).edit(request.data)) @extend_schema( methods=['GET'], description=_('Get folder'), summary=_('Get folder'), operation_id=_('Get folder'), # type: ignore parameters=FolderReadAPI.get_parameters(), responses=FolderReadAPI.get_response(), tags=[_('Folder')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}"), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN ) def get(self, request: Request, workspace_id: str, source: str, folder_id: str): return result.success(FolderSerializer.Operate( data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id} ).one()) @extend_schema( methods=['DELETE'], description=_('Delete folder'), summary=_('Delete folder'), operation_id=_('Delete folder'), # type: ignore parameters=FolderDeleteAPI.get_parameters(), responses=FolderDeleteAPI.get_response(), tags=[_('Folder')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.DELETE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.DELETE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}" ), lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()], [Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.DELETE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}" )], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role() ) @log( menu='folder', operate='Delete folder', get_operation_object=lambda r, k: get_folder_operation_object(k.get('folder_id'), k.get('source')), ) def delete(self, request: Request, workspace_id: str, source: str, folder_id: str): return result.success(FolderSerializer.Operate( data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id} ).delete()) ================================================ FILE: apps/knowledge/__init__.py ================================================ ================================================ FILE: apps/knowledge/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/knowledge/api/__init__.py ================================================ ================================================ FILE: apps/knowledge/api/document.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer from knowledge.serializers.common import BatchSerializer from knowledge.serializers.document import DocumentInstanceSerializer, DocumentWebInstanceSerializer, \ CancelInstanceSerializer, BatchCancelInstanceSerializer, DocumentRefreshSerializer, BatchEditHitHandlingSerializer, \ DocumentBatchRefreshSerializer, DocumentBatchGenerateRelatedSerializer, DocumentMigrateSerializer class DocumentSplitAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return { 'multipart/form-data': { 'type': 'object', 'properties': { 'file': { 'type': 'string', 'format': 'binary' # Tells Swagger it's a file }, 'limit': { 'type': 'integer', 'description': '分段长度' }, 'patterns': { 'type': 'string', 'description': '分段正则列表' }, 'with_filter': { 'type': 'boolean', 'description': '是否清除特殊字符' } } } } class DocumentBatchAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return BatchSerializer @staticmethod def get_response(): return DefaultResultSerializer class DocumentBatchCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return DocumentInstanceSerializer(many=True) @staticmethod def get_response(): return DefaultResultSerializer class DocumentCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return DocumentInstanceSerializer @staticmethod def get_response(): return DefaultResultSerializer class DocumentReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class DocumentEditAPI(DocumentReadAPI): @staticmethod def get_request(): return DocumentInstanceSerializer class DocumentDeleteAPI(DocumentReadAPI): pass class TableDocumentCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return { 'multipart/form-data': { 'type': 'object', 'properties': { 'file': { 'type': 'string', 'format': 'binary' # Tells Swagger it's a file } } } } @staticmethod def get_response(): return DefaultResultSerializer class QaDocumentCreateAPI(TableDocumentCreateAPI): pass class WebDocumentCreateAPI(APIMixin): @staticmethod def get_request(): return DocumentWebInstanceSerializer class CancelTaskAPI(DocumentReadAPI): @staticmethod def get_request(): return CancelInstanceSerializer class BatchCancelTaskAPI(DocumentReadAPI): @staticmethod def get_request(): return BatchCancelInstanceSerializer class SyncWebAPI(DocumentReadAPI): pass class RefreshAPI(DocumentReadAPI): @staticmethod def get_request(): return DocumentRefreshSerializer class BatchEditHitHandlingAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return BatchEditHitHandlingSerializer class DocumentTreeReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="folder_id", description="文件夹id", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="user_id", description="用户id", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="name", description="名称", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="desc", description="描述", type=OpenApiTypes.STR, location='query', required=False, ), ] class DocumentSplitPatternAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class BatchRefreshAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return DocumentBatchRefreshSerializer class BatchGenerateRelatedAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return DocumentBatchGenerateRelatedSerializer class TemplateExportAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="type", description="Export template type csv|excel", type=OpenApiTypes.STR, location='query', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class DocumentExportAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class DocumentMigrateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="target_knowledge_id", description="目标知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return DocumentMigrateSerializer class DocumentDownloadSourceAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class DocumentTagsAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return None @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/api/file.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer class FileUploadAPI(APIMixin): @staticmethod def get_request(): return { 'multipart/form-data': { 'type': 'object', 'properties': { 'file': { 'type': 'string', 'format': 'binary', 'description': '要上传的文件' }, "source_id": { 'type': 'string', 'description': '资源id 如果source_type为[TEMPORARY_30_MINUTE,TEMPORARY_120_MINUTE,TEMPORARY_1_DAY,SYSTEM] 其他的需要为对应资源的id' }, "source_type": { 'type': 'string', 'description': '资源类型[KNOWLEDGE,APPLICATION,TOOL,DOCUMENT,CHAT,SYSTEM,TEMPORARY_30_MINUTE,TEMPORARY_120_MINUTE,TEMPORARY_1_DAY]' } } } } @staticmethod def get_response(): return DefaultResultSerializer class FileGetAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="file_id", description="文件id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/api/knowledge.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, DefaultResultSerializer from knowledge.serializers.common import GenerateRelatedSerializer from knowledge.serializers.knowledge import KnowledgeBaseCreateRequest, KnowledgeModelSerializer, KnowledgeEditRequest, \ KnowledgeWebCreateRequest, HitTestSerializer class KnowledgeCreateResponse(ResultSerializer): def get_data(self): return KnowledgeModelSerializer() class KnowledgeReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return KnowledgeCreateResponse class KnowledgeBaseCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return KnowledgeBaseCreateRequest @staticmethod def get_response(): return KnowledgeCreateResponse class KnowledgeWebCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return KnowledgeWebCreateRequest @staticmethod def get_response(): return KnowledgeCreateResponse class KnowledgeEditAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return KnowledgeEditRequest @staticmethod def get_response(): return KnowledgeCreateResponse class KnowledgeTreeReadAPI(KnowledgeReadAPI): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="folder_id", description="文件夹id", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="user_id", description="用户id", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="name", description="名称", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="desc", description="描述", type=OpenApiTypes.STR, location='query', required=False, ), ] class KnowledgePageAPI(KnowledgeReadAPI): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description="当前页码", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description="每页条数", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="folder_id", description="文件夹id", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="name", description="名称", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="desc", description="描述", type=OpenApiTypes.STR, location='query', required=False, ), ] class SyncWebAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class GenerateRelatedAPI(SyncWebAPI): @staticmethod def get_request(): return GenerateRelatedSerializer class HitTestAPI(SyncWebAPI): @staticmethod def get_request(): return HitTestSerializer class EmbeddingAPI(SyncWebAPI): pass class GetModelAPI(SyncWebAPI): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class KnowledgeExportAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/api/knowledge_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py @date:2025/6/4 17:33 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer from knowledge.serializers.knowledge_version import KnowledgeVersionModelSerializer class KnowledgeListVersionResult(ResultSerializer): def get_data(self): return KnowledgeVersionModelSerializer(many=True) class KnowledgePageVersionResult(ResultPageSerializer): def get_data(self): return KnowledgeVersionModelSerializer(many=True) class KnowledgeWorkflowVersionResult(ResultSerializer): def get_data(self): return KnowledgeVersionModelSerializer() class KnowledgeVersionAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="knowledge ID", type=OpenApiTypes.STR, location='path', required=True, ) ] class KnowledgeVersionOperateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="knowledge_version_id", description="工作流版本id", type=OpenApiTypes.STR, location='path', required=True, ) , *KnowledgeVersionAPI.get_parameters() ] @staticmethod def get_response(): return KnowledgeWorkflowVersionResult class KnowledgeVersionListAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="name", description="Version Name", type=OpenApiTypes.STR, required=False, ) , *KnowledgeVersionOperateAPI.get_parameters()] @staticmethod def get_response(): return KnowledgeListVersionResult class KnowledgeVersionPageAPI(APIMixin): @staticmethod def get_parameters(): return KnowledgeVersionListAPI.get_parameters() @staticmethod def get_response(): return KnowledgePageVersionResult ================================================ FILE: apps/knowledge/api/knowledge_workflow.py ================================================ # coding=utf-8 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionRequestSerializer, \ KnowledgeWorkflowImportRequest from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionListQuerySerializer class KnowledgeWorkflowApi(APIMixin): pass class KnowledgeWorkflowVersionApi(APIMixin): pass class KnowledgeWorkflowActionPageApi(APIMixin): @staticmethod def get_request(): return KnowledgeWorkflowActionListQuerySerializer class KnowledgeWorkflowActionApi(APIMixin): @staticmethod def get_request(): return KnowledgeWorkflowActionRequestSerializer @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ) ] class Operate(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_action_id", description="知识库执行id", type=OpenApiTypes.STR, location='path', required=True, ) ] class KnowledgeWorkflowExportApi(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class KnowledgeWorkflowImportApi(APIMixin): @staticmethod def get_parameters(): return KnowledgeWorkflowExportApi.get_parameters() @staticmethod def get_request(): return KnowledgeWorkflowImportRequest @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/api/paragraph.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer, ResultSerializer from knowledge.serializers.common import BatchSerializer from knowledge.serializers.paragraph import ParagraphSerializer, ParagraphBatchGenerateRelatedSerializer from knowledge.serializers.problem import ProblemSerializer class ParagraphReadResponse(ResultSerializer): @staticmethod def get_data(): return ParagraphSerializer(many=True) class ParagraphReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="title", description="标题", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="content", description="内容", type=OpenApiTypes.STR, location='query', required=False, ), ] @staticmethod def get_response(): return ParagraphReadResponse class ParagraphCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return ParagraphSerializer @staticmethod def get_response(): return ParagraphReadResponse class ParagraphBatchDeleteAPI(ParagraphCreateAPI): @staticmethod def get_request(): return BatchSerializer @staticmethod def get_response(): return DefaultResultSerializer class ParagraphBatchGenerateRelatedAPI(ParagraphCreateAPI): @staticmethod def get_request(): return ParagraphBatchGenerateRelatedSerializer @staticmethod def get_response(): return DefaultResultSerializer class ParagraphGetAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="paragraph_id", description="段落id", type=OpenApiTypes.STR, location='path', required=True, ), ] class ParagraphEditAPI(ParagraphGetAPI): @staticmethod def get_request(): return ParagraphSerializer @staticmethod def get_response(): return DefaultResultSerializer class ProblemCreateAPI(ParagraphGetAPI): @staticmethod def get_request(): return ProblemSerializer @staticmethod def get_response(): return DefaultResultSerializer class UnAssociationAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="paragraph_id", description="段落id", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="problem_id", description="问题id", type=OpenApiTypes.STR, location='query', required=True, ) ] class AssociationAPI(UnAssociationAPI): pass class ParagraphPageAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="paragraph_id", description="段落id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description="当前页", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description="每页大小", type=OpenApiTypes.INT, location='path', required=True, ), ] class ParagraphMigrateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="target_knowledge_id", description="目标知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="target_document_id", description="目标文档id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return BatchSerializer class ParagraphAdjustOrderAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="document_id", description="文档id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="paragraph_id", description="段落id", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="new_position", description="新的顺序", type=OpenApiTypes.INT, location='query', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/api/problem.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer from knowledge.serializers.problem import BatchAssociation, ProblemEditSerializer class ProblemReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class ProblemBatchCreateAPI(ProblemReadAPI): @staticmethod def get_request(): return serializers.ListField(required=True, label=_('problem list'), child=serializers.UUIDField(required=True, label=_('problem'))) class BatchAssociationAPI(ProblemReadAPI): @staticmethod def get_request(): return BatchAssociation class BatchDeleteAPI(ProblemReadAPI): @staticmethod def get_request(): return serializers.ListField(required=True, label=_('problem list'), child=serializers.UUIDField(required=True, label=_('problem'))) class ProblemPageAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description="当前页码", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description="每页条数", type=OpenApiTypes.INT, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class ProblemDeleteAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="problem_id", description="问题id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return DefaultResultSerializer class ProblemEditAPI(ProblemDeleteAPI): @staticmethod def get_request(): return ProblemEditSerializer class ProblemParagraphAPI(ProblemDeleteAPI): pass ================================================ FILE: apps/knowledge/api/tag.py ================================================ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer from knowledge.serializers.common import BatchSerializer from knowledge.serializers.tag import TagCreateSerializer, TagEditSerializer class TagCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TagCreateSerializer @staticmethod def get_response(): return DefaultResultSerializer class TagDeleteAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tag_id", description="标签id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return None @staticmethod def get_response(): return DefaultResultSerializer class DocsTagDeleteAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tag_id", description="标签id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return BatchSerializer @staticmethod def get_response(): return DefaultResultSerializer class TagEditAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="knowledge_id", description="知识库id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tag_id", description="标签id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TagEditSerializer @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/knowledge/apps.py ================================================ from django.apps import AppConfig class KnowledgeConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'knowledge' ================================================ FILE: apps/knowledge/migrations/0001_initial.py ================================================ # Generated by Django 5.2.4 on 2025-07-14 03:50 import django.contrib.postgres.search import django.db.models.deletion import knowledge.models.knowledge import mptt.fields import uuid_utils.compat from django.db import migrations, models from knowledge.models import KnowledgeFolder def insert_default_data(apps, schema_editor): # 创建一个根模块(没有父节点) KnowledgeFolder.objects.create(id='default', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default') class Migration(migrations.Migration): initial = True dependencies = [ ('models_provider', '0001_initial'), ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='File', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('file_name', models.CharField(default='', max_length=256, verbose_name='文件名称')), ('file_size', models.IntegerField(default=0, verbose_name='文件大小')), ('sha256_hash', models.CharField(default='', verbose_name='文件sha256_hash标识')), ('source_type', models.CharField(choices=[('KNOWLEDGE', 'Knowledge'), ('APPLICATION', 'Application'), ('TOOL', 'Tool'), ('DOCUMENT', 'Document'), ('CHAT', 'Chat'), ('TEMPORARY_30_MINUTE', 'Temporary 30 Minute'), ('TEMPORARY_120_MINUTE', 'Temporary 120 Minute'), ('TEMPORARY_1_DAY', 'Temporary 1 Day')], db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源类型')), ('source_id', models.CharField(db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源id')), ('loid', models.IntegerField(verbose_name='loid')), ('meta', models.JSONField(default=dict, verbose_name='文件关联数据')), ], options={ 'db_table': 'file', }, ), migrations.CreateModel( name='Knowledge', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=150, verbose_name='知识库名称')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('desc', models.CharField(max_length=256, verbose_name='描述')), ('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], db_index=True, default=0, verbose_name='类型')), ('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], db_index=True, default='WORKSPACE', max_length=20, verbose_name='可用范围')), ('file_size_limit', models.IntegerField(default=100, verbose_name='文件大小限制')), ('file_count_limit', models.IntegerField(default=50, verbose_name='文件数量限制')), ('meta', models.JSONField(default=dict, verbose_name='元数据')), ('embedding_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'knowledge', }, ), migrations.CreateModel( name='Document', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=150, verbose_name='文档名称')), ('char_length', models.IntegerField(verbose_name='文档字符数 冗余字段')), ('status', models.CharField(db_index=True, default=knowledge.models.knowledge.get_default_status, max_length=20, verbose_name='状态')), ('status_meta', models.JSONField(default=knowledge.models.knowledge.default_status_meta, verbose_name='状态统计数据')), ('is_active', models.BooleanField(db_index=True, default=True)), ('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], db_index=True, default=0, verbose_name='类型')), ('hit_handling_method', models.CharField(choices=[('optimization', '模型优化'), ('directly_return', '直接返回')], default='optimization', max_length=20, verbose_name='命中处理方式')), ('directly_return_similarity', models.FloatField(default=0.9, verbose_name='直接回答相似度')), ('meta', models.JSONField(default=dict, verbose_name='元数据')), ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库id')), ], options={ 'db_table': 'document', }, ), migrations.CreateModel( name='KnowledgeFolder', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')), ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('lft', models.PositiveIntegerField(editable=False)), ('rght', models.PositiveIntegerField(editable=False)), ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), ('level', models.PositiveIntegerField(editable=False)), ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='knowledge.knowledgefolder')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'knowledge_folder', }, ), migrations.AddField( model_name='knowledge', name='folder', field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledgefolder', verbose_name='文件夹id'), ), migrations.CreateModel( name='Paragraph', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('content', models.CharField(max_length=102400, verbose_name='段落内容')), ('title', models.CharField(db_index=True, default='', max_length=256, verbose_name='标题')), ('status', models.CharField(db_index=True, default=knowledge.models.knowledge.get_default_status, max_length=20, verbose_name='状态')), ('status_meta', models.JSONField(default=knowledge.models.knowledge.default_status_meta, verbose_name='状态数据')), ('hit_num', models.IntegerField(default=0, verbose_name='命中次数')), ('is_active', models.BooleanField(db_index=True, default=True)), ('position', models.IntegerField(db_index=True, default=0, verbose_name='段落顺序')), ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document')), ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')), ], options={ 'db_table': 'paragraph', }, ), migrations.CreateModel( name='Embedding', fields=[ ('id', models.CharField(max_length=128, primary_key=True, serialize=False, verbose_name='主键id')), ('source_id', models.CharField(db_index=True, max_length=128, verbose_name='资源id')), ('source_type', models.CharField(choices=[(0, '问题'), (1, '段落'), (2, '标题')], db_index=True, default=0, max_length=5, verbose_name='资源类型')), ('is_active', models.BooleanField(default=True, max_length=1, verbose_name='是否可用')), ('embedding', knowledge.models.knowledge.VectorField(verbose_name='向量')), ('search_vector', django.contrib.postgres.search.SearchVectorField(default='', verbose_name='分词')), ('meta', models.JSONField(default=dict, verbose_name='元数据')), ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document', verbose_name='文档关联')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='文档关联')), ('paragraph', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.paragraph', verbose_name='段落关联')), ], options={ 'db_table': 'embedding', }, ), migrations.CreateModel( name='Problem', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('content', models.CharField(db_index=True, max_length=256, verbose_name='问题内容')), ('hit_num', models.IntegerField(default=0, verbose_name='命中次数')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')), ], options={ 'db_table': 'problem', }, ), migrations.CreateModel( name='ProblemParagraphMapping', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')), ('paragraph', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.paragraph')), ('problem', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.problem')), ], options={ 'db_table': 'problem_paragraph_mapping', }, ), migrations.RunPython(insert_default_data), ] ================================================ FILE: apps/knowledge/migrations/0002_alter_file_source_type.py ================================================ # Generated by Django 5.2.4 on 2025-08-11 09:45 from django.db import migrations, models, connection def add_allow_download_to_existing_documents(apps, schema_editor): # 使用原生SQL进行批量更新,避免加载大量对象到内存 with connection.cursor() as cursor: # 为meta为null的记录设置初始值 cursor.execute(""" UPDATE document SET meta = '{"allow_download": true}'::jsonb WHERE meta IS NULL """) # 为meta不包含allow_download键的记录添加该字段 cursor.execute(""" UPDATE document SET meta = meta || '{"allow_download": true}'::jsonb WHERE meta IS NOT NULL AND NOT (meta ? 'allow_download') """) class Migration(migrations.Migration): dependencies = [ ('knowledge', '0001_initial'), ] operations = [ migrations.AlterField( model_name='file', name='source_type', field=models.CharField(choices=[('KNOWLEDGE', 'Knowledge'), ('APPLICATION', 'Application'), ('TOOL', 'Tool'), ('DOCUMENT', 'Document'), ('CHAT', 'Chat'), ('SYSTEM', 'System'), ('TEMPORARY_30_MINUTE', 'Temporary 30 Minute'), ('TEMPORARY_120_MINUTE', 'Temporary 120 Minute'), ('TEMPORARY_1_DAY', 'Temporary 1 Day')], db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源类型'), ), migrations.RunPython(add_allow_download_to_existing_documents) ] ================================================ FILE: apps/knowledge/migrations/0003_tag_documenttag.py ================================================ # Generated by Django 5.2.7 on 2025-10-11 07:38 import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('knowledge', '0002_alter_file_source_type'), ] operations = [ migrations.CreateModel( name='Tag', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('key', models.CharField(db_index=True, max_length=64, verbose_name='标签键')), ('value', models.CharField(db_index=True, max_length=128, verbose_name='标签值')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库')), ], options={ 'db_table': 'tag', }, ), migrations.CreateModel( name='DocumentTag', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document', verbose_name='文档')), ('tag', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.tag', verbose_name='标签')), ], options={ 'db_table': 'document_tag', }, ), migrations.AddIndex( model_name='tag', index=models.Index(fields=['knowledge', 'key'], name='tag_knowled_cba590_idx'), ), migrations.AlterUniqueTogether( name='tag', unique_together={('knowledge', 'key', 'value')}, ), migrations.AlterUniqueTogether( name='documenttag', unique_together={('document', 'tag')}, ), ] ================================================ FILE: apps/knowledge/migrations/0004_alter_document_type_alter_knowledge_type_and_more.py ================================================ # Generated by Django 5.2.4 on 2025-11-04 05:54 import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('knowledge', '0003_tag_documenttag'), ] operations = [ migrations.AlterField( model_name='document', name='type', field=models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型'), (4, '工作流类型')], db_index=True, default=0, verbose_name='类型'), ), migrations.AlterField( model_name='knowledge', name='type', field=models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型'), (4, '工作流类型')], db_index=True, default=0, verbose_name='类型'), ), migrations.CreateModel( name='KnowledgeWorkflow', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('is_publish', models.BooleanField(db_index=True, default=False, verbose_name='是否发布')), ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')), ('knowledge', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='workflow', to='knowledge.knowledge', verbose_name='知识库')), ], options={ 'db_table': 'knowledge_workflow', }, ), migrations.CreateModel( name='KnowledgeWorkflowVersion', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')), ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='knowledge.knowledge', verbose_name='知识库')), ('workflow', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='knowledge.knowledgeworkflow', verbose_name='工作流')), ], options={ 'db_table': 'knowledge_workflow_version', }, ), ] ================================================ FILE: apps/knowledge/migrations/0005_knowledgeaction.py ================================================ # Generated by Django 5.2.8 on 2025-11-19 06:06 import common.encoder.encoder import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('knowledge', '0004_alter_document_type_alter_knowledge_type_and_more'), ] operations = [ migrations.CreateModel( name='KnowledgeAction', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')), ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='执行详情')), ('run_time', models.FloatField(default=0, verbose_name='运行时长')), ('meta', models.JSONField(default=dict, verbose_name='元数据')), ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库')), ], options={ 'db_table': 'knowledge_action', }, ), ] ================================================ FILE: apps/knowledge/migrations/0006_paragraph_chunks.py ================================================ # Generated by Django 5.2.8 on 2025-11-24 07:09 import django.contrib.postgres.fields from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('knowledge', '0005_knowledgeaction'), ] operations = [ migrations.AddField( model_name='paragraph', name='chunks', field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(), default=list, size=None, verbose_name='块'), ), ] ================================================ FILE: apps/knowledge/migrations/0007_remove_knowledgeworkflowversion_workflow_and_more.py ================================================ # Generated by Django 5.2.8 on 2025-12-01 06:28 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('knowledge', '0006_paragraph_chunks'), ] operations = [ migrations.RemoveField( model_name='knowledgeworkflowversion', name='workflow', ), migrations.AddField( model_name='knowledgeworkflowversion', name='name', field=models.CharField(default='', max_length=128, verbose_name='版本名称'), ), ] ================================================ FILE: apps/knowledge/migrations/__init__.py ================================================ ================================================ FILE: apps/knowledge/models/__init__.py ================================================ from .knowledge import * ================================================ FILE: apps/knowledge/models/knowledge.py ================================================ import io import zipfile from enum import Enum import uuid_utils.compat as uuid from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.search import SearchVectorField from django.db import models from django.db.models import QuerySet from django.db.models.signals import pre_delete from django.dispatch import receiver from mptt.fields import TreeForeignKey from mptt.models import MPTTModel from common.db.sql_execute import select_one from common.mixins.app_model_mixin import AppModelMixin from common.utils.common import get_sha256_hash from models_provider.models import Model from users.models import User class KnowledgeType(models.IntegerChoices): BASE = 0, '通用类型' WEB = 1, 'web站点类型' LARK = 2, '飞书类型' YUQUE = 3, '语雀类型' WORKFLOW = 4, '工作流类型' class TaskType(Enum): # 向量 EMBEDDING = 1 # 生成问题 GENERATE_PROBLEM = 2 # 同步 SYNC = 3 class State(Enum): # 等待 PENDING = '0' # 执行中 STARTED = '1' # 成功 SUCCESS = '2' # 失败 FAILURE = '3' # 取消任务 REVOKE = '4' # 取消成功 REVOKED = '5' # 忽略 IGNORED = 'n' class KnowledgeScope(models.TextChoices): SHARED = "SHARED", '共享' WORKSPACE = "WORKSPACE", "工作空间可用" class HitHandlingMethod(models.TextChoices): optimization = 'optimization', '模型优化' directly_return = 'directly_return', '直接返回' class Status: type_cls = TaskType state_cls = State def __init__(self, status: str = None): self.task_status = {} status_list = list(status[::-1] if status is not None else '') for _type in self.type_cls: index = _type.value - 1 _state = self.state_cls(status_list[index] if len(status_list) > index else 'n') self.task_status[_type] = _state @staticmethod def of(status: str): return Status(status) def __str__(self): result = [] for _type in sorted(self.type_cls, key=lambda item: item.value, reverse=True): result.insert(len(self.type_cls) - _type.value, self.task_status[_type].value) return ''.join(result) def __setitem__(self, key, value): self.task_status[key] = value def __getitem__(self, item): return self.task_status[item] def update_status(self, task_type: TaskType, state: State): self.task_status[task_type] = state def default_status_meta(): return {"state_time": {}} class KnowledgeFolder(MPTTModel, AppModelMixin): id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") name = models.CharField(max_length=64, verbose_name="文件夹名称", db_index=True) desc = models.CharField(max_length=200, null=True, blank=True, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children') class Meta: db_table = "knowledge_folder" class MPTTMeta: order_insertion_by = ['name'] class Knowledge(AppModelMixin): """ 知识库表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") name = models.CharField(max_length=150, verbose_name="知识库名称", db_index=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) desc = models.CharField(max_length=256, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE, db_index=True) scope = models.CharField(max_length=20, verbose_name='可用范围', choices=KnowledgeScope.choices, default=KnowledgeScope.WORKSPACE, db_index=True) folder = models.ForeignKey(KnowledgeFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='default') embedding_model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) file_size_limit = models.IntegerField(verbose_name="文件大小限制", default=100) file_count_limit = models.IntegerField(verbose_name="文件数量限制", default=50) meta = models.JSONField(verbose_name="元数据", default=dict) class Meta: db_table = "knowledge" class KnowledgeWorkflow(AppModelMixin): """ 知识库工作流表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.OneToOneField(Knowledge, on_delete=models.CASCADE, verbose_name="知识库", db_constraint=False, related_name='workflow') workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) work_flow = models.JSONField(verbose_name="工作流数据", default=dict) is_publish = models.BooleanField(verbose_name="是否发布", default=False, db_index=True) publish_time = models.DateTimeField(verbose_name="发布时间", null=True, blank=True) class Meta: db_table = "knowledge_workflow" class KnowledgeWorkflowVersion(AppModelMixin): """ 知识库工作流版本表 - 记录工作流历史版本 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.CASCADE, verbose_name="知识库", db_constraint=False) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) name = models.CharField(verbose_name="版本名称", max_length=128, default="") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True) publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="") class Meta: db_table = "knowledge_workflow_version" def get_default_status(): return Status('').__str__() class Document(AppModelMixin): """ 文档表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name="知识库id") name = models.CharField(max_length=150, verbose_name="文档名称", db_index=True) char_length = models.IntegerField(verbose_name="文档字符数 冗余字段") status = models.CharField(verbose_name='状态', max_length=20, default=get_default_status, db_index=True) status_meta = models.JSONField(verbose_name="状态统计数据", default=default_status_meta) is_active = models.BooleanField(default=True, db_index=True) type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE, db_index=True) hit_handling_method = models.CharField(verbose_name='命中处理方式', max_length=20, choices=HitHandlingMethod.choices, default=HitHandlingMethod.optimization) directly_return_similarity = models.FloatField(verbose_name='直接回答相似度', default=0.9) meta = models.JSONField(verbose_name="元数据", default=dict) class Meta: db_table = "document" class Tag(AppModelMixin): """ 标签表 - 存储标签的key-value定义 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name="知识库", db_constraint=False) key = models.CharField(max_length=64, verbose_name="标签键", db_index=True) value = models.CharField(max_length=128, verbose_name="标签值", db_index=True) class Meta: db_table = "tag" unique_together = [['knowledge', 'key', 'value']] # 在同一知识库内key-value组合唯一 indexes = [ models.Index(fields=['knowledge', 'key']), ] class DocumentTag(AppModelMixin): """ 文档标签关联表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, verbose_name="文档", db_constraint=False) tag = models.ForeignKey(Tag, on_delete=models.DO_NOTHING, verbose_name="标签", db_constraint=False) class Meta: db_table = "document_tag" unique_together = [['document', 'tag']] # 文档和标签的组合唯一 class Paragraph(AppModelMixin): """ 段落表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, db_constraint=False) knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING) content = models.CharField(max_length=102400, verbose_name="段落内容") title = models.CharField(max_length=256, verbose_name="标题", default="", db_index=True) status = models.CharField(verbose_name='状态', max_length=20, default=get_default_status, db_index=True) status_meta = models.JSONField(verbose_name="状态数据", default=default_status_meta) hit_num = models.IntegerField(verbose_name="命中次数", default=0) is_active = models.BooleanField(default=True, db_index=True) position = models.IntegerField(verbose_name="段落顺序", default=0, db_index=True) chunks = ArrayField(verbose_name="块", base_field=models.CharField(), default=list) class Meta: db_table = "paragraph" class Problem(AppModelMixin): """ 问题表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, db_constraint=False) content = models.CharField(max_length=256, verbose_name="问题内容", db_index=True) hit_num = models.IntegerField(verbose_name="命中次数", default=0) class Meta: db_table = "problem" class ProblemParagraphMapping(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, db_constraint=False) document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, db_constraint=False) problem = models.ForeignKey(Problem, on_delete=models.DO_NOTHING, db_constraint=False) paragraph = models.ForeignKey(Paragraph, on_delete=models.DO_NOTHING, db_constraint=False) class Meta: db_table = "problem_paragraph_mapping" class SourceType(models.IntegerChoices): """订单类型""" PROBLEM = 0, '问题' PARAGRAPH = 1, '段落' TITLE = 2, '标题' class SearchMode(models.TextChoices): embedding = 'embedding' keywords = 'keywords' blend = 'blend' class FileSourceType(models.TextChoices): # 知识库 跟随知识库被删除而被删除 source_id 为知识库id KNOWLEDGE = "KNOWLEDGE" # 应用 跟随应用被删除而被删除 source_id 为应用id APPLICATION = "APPLICATION" # 工具 跟随工具被删除而被删除 source_id 为应用id TOOL = "TOOL" # 文档 DOCUMENT = "DOCUMENT" # 对话 CHAT = "CHAT" SYSTEM = "SYSTEM" # 临时30分钟 数据30分钟后被清理 source_id 为TEMPORARY_30_MINUTE TEMPORARY_30_MINUTE = "TEMPORARY_30_MINUTE" # 临时120分钟 数据120分钟后被清理 source_id为TEMPORARY_100_MINUTE TEMPORARY_120_MINUTE = "TEMPORARY_120_MINUTE" # 临时1天 数据1天后被清理 source_id为TEMPORARY_1_DAY TEMPORARY_1_DAY = "TEMPORARY_1_DAY" class VectorField(models.Field): def db_type(self, connection): return 'vector' class Embedding(models.Model): id = models.CharField(max_length=128, primary_key=True, verbose_name="主键id") source_id = models.CharField(max_length=128, verbose_name="资源id", db_index=True) source_type = models.CharField(verbose_name='资源类型', max_length=5, choices=SourceType.choices, default=SourceType.PROBLEM, db_index=True) is_active = models.BooleanField(verbose_name="是否可用", max_length=1, default=True) knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name="文档关联", db_constraint=False) document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, verbose_name="文档关联", db_constraint=False) paragraph = models.ForeignKey(Paragraph, on_delete=models.DO_NOTHING, verbose_name="段落关联", db_constraint=False) embedding = VectorField(verbose_name="向量") search_vector = SearchVectorField(verbose_name="分词", default="") meta = models.JSONField(verbose_name="元数据", default=dict) class Meta: db_table = "embedding" class File(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") file_name = models.CharField(max_length=256, verbose_name="文件名称", default="") file_size = models.IntegerField(verbose_name="文件大小", default=0) sha256_hash = models.CharField(verbose_name="文件sha256_hash标识", default="") source_type = models.CharField(verbose_name="资源类型", choices=FileSourceType, default=FileSourceType.TEMPORARY_120_MINUTE.value, db_index=True) source_id = models.CharField(verbose_name="资源id", default=FileSourceType.TEMPORARY_120_MINUTE.value, db_index=True) loid = models.IntegerField(verbose_name="loid") meta = models.JSONField(verbose_name="文件关联数据", default=dict) class Meta: db_table = "file" def save(self, bytea=None, force_insert=False, force_update=False, using=None, update_fields=None): if bytea is None: raise ValueError("bytea参数不能为空") sha256_hash = get_sha256_hash(bytea) self.sha256_hash = sha256_hash existing_file = QuerySet(File).filter(sha256_hash=sha256_hash).first() if existing_file: self.loid = existing_file.loid self.file_size = existing_file.file_size return super().save() compressed_data = self._compress_data(bytea) self.file_size = len(compressed_data) self.loid = self._create_large_object() self._write_compressed_data(compressed_data) # 调用父类保存 return super().save() def _compress_data(self, data, compression_level=9): """压缩数据到内存""" buffer = io.BytesIO() with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: zipinfo = zipfile.ZipInfo(self.file_name) zipinfo.compress_type = zipfile.ZIP_DEFLATED zip_file.writestr(zipinfo, data, compresslevel=compression_level) return buffer.getvalue() def _create_large_object(self): result = select_one("SELECT lo_creat(-1)::int8 as lo_id;", []) return result['lo_id'] def _write_compressed_data(self, data, block_size=64 * 1024): buffer = io.BytesIO(data) offset = 0 while True: chunk = buffer.read(block_size) if not chunk: break offset += len(chunk) select_one( "SELECT lo_put(%s::oid, %s::bigint, %s::bytea)::VARCHAR;", [self.loid, offset - len(chunk), chunk] ) def get_bytes(self): buffer = io.BytesIO() for chunk in self.get_bytes_stream(): buffer.write(chunk) try: # 解压数据 with zipfile.ZipFile(buffer) as zip_file: # 用 zip 内实际存储的条目名,避免文件名不匹配 name = zip_file.namelist()[0] return zip_file.read(name) except Exception as e: # 如果数据不是zip格式,直接返回原始数据 return buffer.getvalue() def get_bytes_stream(self, start=0, end=None, chunk_size=64 * 1024): def _read_with_offset(): offset = start while True: result = select_one( "SELECT lo_get(%s::oid, %s, %s) as chunk", [self.loid, offset, end - offset if end and (end - offset) < chunk_size else chunk_size] ) chunk = result['chunk'] if result else None if not chunk: break yield chunk offset += len(chunk) if len(chunk) < chunk_size: break if end and offset > end: break return _read_with_offset() @receiver(pre_delete, sender=File) def on_delete_file(sender, instance, **kwargs): exist = QuerySet(File).filter(loid=instance.loid).exclude(id=instance.id).exists() if not exist: select_one(f'SELECT lo_unlink({instance.loid})', []) ================================================ FILE: apps/knowledge/models/knowledge_action.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: knowledge_action.py @date:2025/11/18 17:59 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.encoder.encoder import SystemEncoder from common.mixins.app_model_mixin import AppModelMixin from knowledge.models import Knowledge class State(models.TextChoices): # 等待 PENDING = 'PENDING' # 执行中 STARTED = 'STARTED' # 成功 SUCCESS = 'SUCCESS' # 失败 FAILURE = 'FAILURE' # 取消任务 REVOKE = 'REVOKE' # 取消成功 REVOKED = 'REVOKED' class KnowledgeAction(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name="知识库", db_constraint=False) state = models.CharField(verbose_name='状态', max_length=20, choices=State.choices, default=State.STARTED) details = models.JSONField(verbose_name="执行详情", default=dict, encoder=SystemEncoder) run_time = models.FloatField(verbose_name="运行时长", default=0) meta = models.JSONField(verbose_name="元数据", default=dict) class Meta: db_table = "knowledge_action" ================================================ FILE: apps/knowledge/serializers/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/knowledge/serializers/common.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: common_serializers.py @date:2023/11/17 11:00 @desc: """ import os import re import zipfile from typing import List import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.flow.tools import save_workflow_mapping, get_instance_resource, knowledge_instance_field_call_dict from common.config.embedding_config import ModelManage from common.db.search import native_search from common.db.sql_execute import sql_execute, update_execute from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from common.utils.fork import Fork from common.utils.logger import maxkb_logger from knowledge.models import Document, KnowledgeWorkflow, KnowledgeWorkflowVersion, KnowledgeType from knowledge.models import Paragraph, Problem, ProblemParagraphMapping, Knowledge, File from maxkb.conf import PROJECT_DIR from models_provider.tools import get_model, get_model_default_params from system_manage.models.resource_mapping import ResourceMapping, ResourceType class MetaSerializer(serializers.Serializer): class WebMeta(serializers.Serializer): source_url = serializers.CharField(required=True, label=_('source url')) selector = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('selector')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) source_url = self.data.get('source_url') response = Fork(source_url, []).fork() if response.status == 500: raise AppApiException(500, _('URL error, cannot parse [{source_url}]').format(source_url=source_url)) class BaseMeta(serializers.Serializer): def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) class BatchSerializer(serializers.Serializer): id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list')) def is_valid(self, *, model=None, raise_exception=False): super().is_valid(raise_exception=True) if model is not None: id_list = self.data.get('id_list') model_list = QuerySet(model).filter(id__in=id_list) if len(model_list) != len(id_list): model_id_list = [str(m.id) for m in model_list] error_id_list = list(filter(lambda row_id: not model_id_list.__contains__(row_id), id_list)) raise AppApiException(500, _('The following id does not exist: {error_id_list}').format( error_id_list=error_id_list)) class ProblemParagraphObject: def __init__(self, knowledge_id: str, document_id: str, paragraph_id: str, problem_content: str): self.knowledge_id = knowledge_id self.document_id = document_id self.paragraph_id = paragraph_id self.problem_content = problem_content class GenerateRelatedSerializer(serializers.Serializer): model_id = serializers.UUIDField(required=True, label=_('Model id')) prompt = serializers.CharField(required=True, label=_('Prompt word')) state_list = serializers.ListField(required=False, child=serializers.CharField(required=True), label=_("state list")) class ProblemParagraphManage: def __init__(self, problem_paragraph_object_list: List[ProblemParagraphObject], knowledge_id): self.knowledge_id = knowledge_id self.problem_paragraph_object_list = problem_paragraph_object_list def to_problem_model_list(self): problem_list = [item.problem_content for item in self.problem_paragraph_object_list] exists_problem_list = [] if len(self.problem_paragraph_object_list) > 0: # 查询到已存在的问题列表 exists_problem_list = QuerySet(Problem).filter(knowledge_id=self.knowledge_id, content__in=problem_list).all() problem_content_dict = {} problem_model_list = [ or_get( exists_problem_list, problemParagraphObject.problem_content, problemParagraphObject.knowledge_id, problemParagraphObject.document_id, problemParagraphObject.paragraph_id, problem_content_dict ) for problemParagraphObject in self.problem_paragraph_object_list] problem_paragraph_mapping_list = [ ProblemParagraphMapping( id=uuid.uuid7(), document_id=document_id, problem_id=problem_model.id, paragraph_id=paragraph_id, knowledge_id=self.knowledge_id ) for problem_model, document_id, paragraph_id in problem_model_list] result = [ problem_model for problem_model, is_create in problem_content_dict.values() if is_create ], problem_paragraph_mapping_list return result def get_embedding_model_by_knowledge_id_list(knowledge_id_list: List): knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list) if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1: raise Exception(_('The knowledge base is inconsistent with the vector model')) if len(knowledge_list) == 0: raise Exception(_('Knowledge base setting error, please reset the knowledge base')) default_params = get_model_default_params(knowledge_list[0].embedding_model) return ModelManage.get_model( str(knowledge_list[0].embedding_model_id), lambda _id: get_model(knowledge_list[0].embedding_model, **{**default_params}) ) def get_embedding_model_by_knowledge_id(knowledge_id: str): knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first() default_params = get_model_default_params(knowledge.embedding_model) return ModelManage.get_model(str(knowledge.embedding_model_id), lambda _id: get_model(knowledge.embedding_model, **{**default_params})) def get_embedding_model_by_knowledge(knowledge): default_params = get_model_default_params(knowledge.embedding_model) return ModelManage.get_model(str(knowledge.embedding_model_id), lambda _id: get_model(knowledge.embedding_model, **{**default_params})) def get_embedding_model_id_by_knowledge_id(knowledge_id): knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first() return str(knowledge.embedding_model_id) def get_embedding_model_id_by_knowledge_id_list(knowledge_id_list: List): knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list) if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1: raise Exception(_('The knowledge base is inconsistent with the vector model')) if len(knowledge_list) == 0: raise Exception(_('Knowledge base setting error, please reset the knowledge base')) return str(knowledge_list[0].embedding_model_id) def zip_dir(zip_path, output=None): output = output or os.path.basename(zip_path) + '.zip' zip = zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) for root, dirs, files in os.walk(zip_path): relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep for filename in files: zip.write(os.path.join(root, filename), relative_root + filename) zip.close() def is_valid_uuid(s): try: uuid.UUID(s) return True except ValueError: return False def write_image(zip_path: str, image_list: List[str]): for image in image_list: search = re.search("\(.*\)", image) if search: text = search.group() if text.startswith('(./oss/file/'): r = text.replace('(./oss/file/', '').replace(')', '') r = r.strip().split(" ")[0] if not is_valid_uuid(r): break file = QuerySet(File).filter(id=r).first() if file is None: break zip_inner_path = os.path.join('oss', 'file', r) file_path = os.path.join(zip_path, zip_inner_path) if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) with open(os.path.join(zip_path, file_path), 'wb') as f: f.write(file.get_bytes()) def update_document_char_length(document_id: str): update_execute(get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'update_document_char_length.sql')), (document_id, document_id)) def list_paragraph(paragraph_list: List[str]): if paragraph_list is None or len(paragraph_list) == 0: return [] return native_search(QuerySet(Paragraph).filter(id__in=paragraph_list), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph.sql'))) def or_get(exists_problem_list, content, knowledge_id, document_id, paragraph_id, problem_content_dict): if content in problem_content_dict: return problem_content_dict.get(content)[0], document_id, paragraph_id exists = [row for row in exists_problem_list if row.content == content] if len(exists) > 0: problem_content_dict[content] = exists[0], False return exists[0], document_id, paragraph_id else: problem = Problem(id=uuid.uuid7(), content=content, knowledge_id=knowledge_id) problem_content_dict[content] = problem, True return problem, document_id, paragraph_id def get_knowledge_operation_object(knowledge_id: str): knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first() if knowledge_model is not None: return { "name": knowledge_model.name, "desc": knowledge_model.desc, "type": knowledge_model.type, "create_time": knowledge_model.create_time, "update_time": knowledge_model.update_time } return {} def create_knowledge_index(knowledge_id=None, document_id=None): if knowledge_id is None and document_id is None: raise AppApiException(500, _('Knowledge ID or Document ID must be provided')) if knowledge_id is not None: k_id = knowledge_id else: document = QuerySet(Document).filter(id=document_id).first() k_id = document.knowledge_id sql = f"SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'embedding' AND indexname = 'embedding_hnsw_idx_{k_id}'" index = sql_execute(sql, []) if not index: sql = f"SELECT vector_dims(embedding) AS dims FROM embedding WHERE knowledge_id = '{k_id}' LIMIT 1" result = sql_execute(sql, []) if len(result) == 0: return dims = result[0]['dims'] # 超过2000维度不创建索引,pgvector hnsw索引不支持超过2000维度 if dims < 2000: sql = f"""CREATE INDEX "embedding_hnsw_idx_{k_id}" ON embedding USING hnsw ((embedding::vector({dims})) vector_cosine_ops) WHERE knowledge_id = '{k_id}'""" update_execute(sql, []) maxkb_logger.info(f'Created index for knowledge ID: {k_id}') def drop_knowledge_index(knowledge_id=None, document_id=None): if knowledge_id is None and document_id is None: raise AppApiException(500, _('Knowledge ID or Document ID must be provided')) if knowledge_id is not None: k_id = knowledge_id else: document = QuerySet(Document).filter(id=document_id).first() k_id = document.knowledge_id sql = f"SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'embedding' AND indexname = 'embedding_hnsw_idx_{k_id}'" index = sql_execute(sql, []) if index: sql = f'DROP INDEX "embedding_hnsw_idx_{k_id}"' update_execute(sql, []) maxkb_logger.info(f'Dropped index for knowledge ID: {k_id}') def update_resource_mapping_by_knowledge(knowledge_id: str): knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() instance_mapping = get_instance_resource(knowledge, ResourceType.KNOWLEDGE, str(knowledge.id), knowledge_instance_field_call_dict) if knowledge.type == KnowledgeType.WORKFLOW: knowledge_workflow = QuerySet(KnowledgeWorkflow).filter( knowledge_id=knowledge_id).order_by( '-create_time')[0:1].first() if knowledge_workflow: save_workflow_mapping(knowledge_workflow.work_flow, ResourceType.KNOWLEDGE, str(knowledge_id), instance_mapping) return else: save_workflow_mapping({}, ResourceType.KNOWLEDGE, str(knowledge_id), instance_mapping) ================================================ FILE: apps/knowledge/serializers/document.py ================================================ import io import json import os import re import traceback from collections import defaultdict from functools import reduce from tempfile import TemporaryDirectory from typing import Dict, List import openpyxl import uuid_utils.compat as uuid from celery_once import AlreadyQueued from django.contrib.postgres.fields import JSONField from django.core import validators from django.db import transaction, models from django.db.models import QuerySet, Func, F, Value from django.db.models.aggregates import Max from django.db.models.functions import Substr, Reverse from django.db.models.query_utils import Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _, gettext, get_language, to_locale from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE from rest_framework import serializers from xlwt import Utils from common.db.search import native_search, get_dynamics_model, native_page_search from common.event.common import work_thread_pool from common.event.listener_manage import ListenerManagement from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField from common.handle.impl.qa.csv_parse_qa_handle import CsvParseQAHandle from common.handle.impl.qa.md_parse_qa_handle import MarkdownParseQAHandle from common.handle.impl.qa.xls_parse_qa_handle import XlsParseQAHandle from common.handle.impl.qa.xlsx_parse_qa_handle import XlsxParseQAHandle from common.handle.impl.qa.zip_parse_qa_handle import ZipParseQAHandle from common.handle.impl.table.csv_parse_table_handle import CsvParseTableHandle from common.handle.impl.table.xls_parse_table_handle import XlsParseTableHandle from common.handle.impl.table.xlsx_parse_table_handle import XlsxParseTableHandle from common.handle.impl.text.csv_split_handle import CsvSplitHandle from common.handle.impl.text.doc_split_handle import DocSplitHandle from common.handle.impl.text.html_split_handle import HTMLSplitHandle from common.handle.impl.text.pdf_split_handle import PdfSplitHandle from common.handle.impl.text.text_split_handle import TextSplitHandle from common.handle.impl.text.xls_split_handle import XlsSplitHandle from common.handle.impl.text.xlsx_split_handle import XlsxSplitHandle from common.handle.impl.text.zip_split_handle import ZipSplitHandle from common.utils.common import post, get_file_content, bulk_create_in_batches, parse_image from common.utils.fork import Fork from common.utils.logger import maxkb_logger from common.utils.split_model import get_split_model, flat_map from knowledge.models import Knowledge, Paragraph, Problem, Document, KnowledgeType, ProblemParagraphMapping, State, \ TaskType, File, FileSourceType, Tag, DocumentTag from knowledge.serializers.common import ProblemParagraphManage, BatchSerializer, \ get_embedding_model_id_by_knowledge_id, MetaSerializer, write_image, zip_dir from knowledge.serializers.paragraph import ParagraphSerializers, ParagraphInstanceSerializer, \ delete_problems_and_mappings from knowledge.task.embedding import embedding_by_document, delete_embedding_by_document_list, \ delete_embedding_by_document, delete_embedding_by_paragraph_ids, embedding_by_document_list, \ update_embedding_knowledge_id from knowledge.task.generate import generate_related_by_document_id from knowledge.task.sync import sync_web_document from maxkb.const import PROJECT_DIR from models_provider.models import Model from oss.serializers.file import FileSerializer default_split_handle = TextSplitHandle() split_handles = [ HTMLSplitHandle(), DocSplitHandle(), PdfSplitHandle(), XlsxSplitHandle(), XlsSplitHandle(), CsvSplitHandle(), ZipSplitHandle(), default_split_handle ] md_qa_split_handle = MarkdownParseQAHandle() parse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle(), ZipParseQAHandle()] parse_table_handle_list = [CsvParseTableHandle(), XlsParseTableHandle(), XlsxParseTableHandle()] def convert_uuid_to_str(obj): if isinstance(obj, dict): return {k: convert_uuid_to_str(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_uuid_to_str(i) for i in obj] elif isinstance(obj, uuid.UUID): return str(obj) else: return obj class BatchCancelInstanceSerializer(serializers.Serializer): id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list')) type = serializers.IntegerField(required=True, label=_('task type')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) _type = self.data.get('type') try: TaskType(_type) except Exception as e: raise AppApiException(500, _('task type not support')) class DocumentInstanceSerializer(serializers.Serializer): name = serializers.CharField(required=True, label=_('document name'), max_length=128, min_length=1, source=_('document name')) paragraphs = ParagraphInstanceSerializer(required=False, many=True, allow_null=True) source_file_id = serializers.UUIDField(required=False, allow_null=True, label=_('source file id')) class CancelInstanceSerializer(serializers.Serializer): type = serializers.IntegerField(required=True, label=_('task type')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) _type = self.data.get('type') try: TaskType(_type) except Exception as e: raise AppApiException(500, _('task type not support')) class DocumentEditInstanceSerializer(serializers.Serializer): meta = serializers.DictField(required=False) name = serializers.CharField(required=False, max_length=128, min_length=1, label=_('document name'), source=_('document name')) hit_handling_method = serializers.CharField(required=False, validators=[ validators.RegexValidator(regex=re.compile("^optimization|directly_return$"), message=_('The type only supports optimization|directly_return'), code=500) ], label=_('hit handling method')) directly_return_similarity = serializers.FloatField(required=False, max_value=2, min_value=0, label=_('directly return similarity')) is_active = serializers.BooleanField(required=False, label=_('document is active')) @staticmethod def get_meta_valid_map(): knowledge_meta_valid_map = { KnowledgeType.BASE: MetaSerializer.BaseMeta, KnowledgeType.WEB: MetaSerializer.WebMeta } return knowledge_meta_valid_map def is_valid(self, *, document: Document = None): super().is_valid(raise_exception=True) if 'meta' in self.data and self.data.get('meta') is not None and self.data.get('meta') != {}: knowledge_meta_valid_map = self.get_meta_valid_map() valid_class = knowledge_meta_valid_map.get(document.type) if valid_class is not None: valid_class(data=self.data.get('meta')).is_valid(raise_exception=True) class DocumentSplitRequest(serializers.Serializer): file = serializers.ListField(required=True, label=_('file list')) limit = serializers.IntegerField(required=False, label=_('limit')) patterns = serializers.ListField( required=False, child=serializers.CharField(required=True, label=_('patterns')), label=_('patterns') ) with_filter = serializers.BooleanField(required=False, label=_('Auto Clean')) class DocumentWebInstanceSerializer(serializers.Serializer): source_url_list = serializers.ListField(required=True, label=_('document url list'), child=serializers.CharField(required=True, label=_('document url list'))) selector = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('selector')) class DocumentInstanceQASerializer(serializers.Serializer): file_list = serializers.ListSerializer(required=True, label=_('file list'), child=serializers.FileField(required=True, label=_('file'))) class DocumentInstanceTableSerializer(serializers.Serializer): file_list = serializers.ListSerializer(required=True, label=_('file list'), child=serializers.FileField(required=True, label=_('file'))) class DocumentRefreshSerializer(serializers.Serializer): state_list = serializers.ListField(required=True, label=_('state list')) class DocumentBatchRefreshSerializer(serializers.Serializer): id_list = serializers.ListField(required=True, label=_('id list')) state_list = serializers.ListField(required=True, label=_('state list')) class DocumentBatchGenerateRelatedSerializer(serializers.Serializer): document_id_list = serializers.ListField(required=True, label=_('document id list')) model_id = serializers.UUIDField(required=True, label=_('model id')) prompt = serializers.CharField(required=True, label=_('prompt')) state_list = serializers.ListField(required=True, label=_('state list')) class DocumentMigrateSerializer(serializers.Serializer): document_id_list = serializers.ListField(required=True, label=_('document id list')) class BatchEditHitHandlingSerializer(serializers.Serializer): id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list')) hit_handling_method = serializers.CharField(required=True, label=_('hit handling method')) directly_return_similarity = serializers.FloatField(required=False, max_value=2, min_value=0, label=_('directly return similarity')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if self.data.get('hit_handling_method') not in ['optimization', 'directly_return']: raise AppApiException(500, _('The type only supports optimization|directly_return')) class DocumentSerializers(serializers.Serializer): class Export(serializers.Serializer): type = serializers.CharField(required=True, validators=[ validators.RegexValidator(regex=re.compile("^csv|excel$"), message=_('The template type only supports excel|csv'), code=500) ], label=_('type')) def export(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': file = open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'csv_template_{to_locale(language)}.csv'), "rb") content = file.read() file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'excel_template_{to_locale(language)}.xlsx'), "rb") content = file.read() file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: return None def table_export(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': file = open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'table_template_{to_locale(language)}.csv'), "rb") content = file.read() file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'text/cxv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'table_template_{to_locale(language)}.xlsx'), "rb") content = file.read() file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: return None class Migrate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) target_knowledge_id = serializers.UUIDField(required=True, label=_('target knowledge id')) document_id_list = serializers.ListField(required=True, label=_('document list'), child=serializers.UUIDField(required=True, label=_('document id'))) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) query_set = QuerySet(Knowledge).filter(id=self.data.get('target_knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def migrate(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') target_knowledge_id = self.data.get('target_knowledge_id') knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() target_knowledge = QuerySet(Knowledge).filter(id=target_knowledge_id).first() document_id_list = self.data.get('document_id_list') document_list = QuerySet(Document).filter(knowledge_id=knowledge_id, id__in=document_id_list) paragraph_list = QuerySet(Paragraph).filter(knowledge_id=knowledge_id, document_id__in=document_id_list) problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(paragraph__in=paragraph_list) problem_list = QuerySet(Problem).filter( id__in=[problem_paragraph_mapping.problem_id for problem_paragraph_mapping in problem_paragraph_mapping_list]) target_problem_list = list( QuerySet(Problem).filter(content__in=[problem.content for problem in problem_list], knowledge_id=target_knowledge_id)) target_handle_problem_list = [ self.get_target_knowledge_problem(target_knowledge_id, problem_paragraph_mapping, problem_list, target_problem_list) for problem_paragraph_mapping in problem_paragraph_mapping_list] create_problem_list = [problem for problem, is_create in target_handle_problem_list if is_create is not None and is_create] # 插入问题 QuerySet(Problem).bulk_create(create_problem_list) # 修改mapping QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list, ['problem_id', 'knowledge_id']) # 修改文档 if knowledge.type == KnowledgeType.BASE.value and target_knowledge.type == KnowledgeType.WEB.value: document_list.update(knowledge_id=target_knowledge_id, type=KnowledgeType.WEB, meta={'source_url': '', 'selector': ''}) elif target_knowledge.type == KnowledgeType.BASE.value and knowledge.type == KnowledgeType.WEB.value: document_list.update(knowledge_id=target_knowledge_id, type=KnowledgeType.BASE, meta={}) else: document_list.update(knowledge_id=target_knowledge_id) model_id = None if knowledge.embedding_model_id != target_knowledge.embedding_model_id: model_id = get_embedding_model_id_by_knowledge_id(target_knowledge_id) pid_list = [paragraph.id for paragraph in paragraph_list] # 修改段落信息 paragraph_list.update(knowledge_id=target_knowledge_id) # 修改向量信息 if model_id: delete_embedding_by_paragraph_ids(pid_list) ListenerManagement.update_status(QuerySet(Document).filter(id__in=document_id_list), TaskType.EMBEDDING, State.PENDING) ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id__in=document_id_list), TaskType.EMBEDDING, State.PENDING) ListenerManagement.get_aggregation_document_status_by_query_set( QuerySet(Document).filter(id__in=document_id_list))() embedding_by_document_list.delay(document_id_list, model_id) else: update_embedding_knowledge_id(pid_list, target_knowledge_id) @staticmethod def get_target_knowledge_problem(target_knowledge_id: str, problem_paragraph_mapping, source_problem_list, target_problem_list): source_problem_list = [source_problem for source_problem in source_problem_list if source_problem.id == problem_paragraph_mapping.problem_id] problem_paragraph_mapping.knowledge_id = target_knowledge_id if len(source_problem_list) > 0: problem_content = source_problem_list[-1].content problem_list = [problem for problem in target_problem_list if problem.content == problem_content] if len(problem_list) > 0: problem = problem_list[-1] problem_paragraph_mapping.problem_id = problem.id return problem, False else: problem = Problem(id=uuid.uuid7(), knowledge_id=target_knowledge_id, content=problem_content) target_problem_list.append(problem) problem_paragraph_mapping.problem_id = problem.id return problem, True return None class Query(serializers.Serializer): # 知识库id workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) name = serializers.CharField( required=False, max_length=128, min_length=1, allow_null=True, allow_blank=True, label=_('document name') ) hit_handling_method = serializers.CharField( required=False, label=_('hit handling method'), allow_null=True, allow_blank=True ) is_active = serializers.BooleanField(required=False, label=_('document is active'), allow_null=True) task_type = serializers.IntegerField(required=False, label=_('task type')) status = serializers.CharField(required=False, label=_('status'), allow_null=True, allow_blank=True) order_by = serializers.CharField(required=False, label=_('order by'), allow_null=True, allow_blank=True) tag = serializers.CharField(required=False, label=_('tag'), allow_null=True, allow_blank=True) tag_ids = serializers.ListField(child=serializers.UUIDField(), allow_null=True, required=False, allow_empty=True) no_tag = serializers.BooleanField(required=False, default=False, allow_null=True) tag_exclude = serializers.BooleanField(required=False, default=False, allow_null=True) def get_query_set(self): query_set = QuerySet(model=Document) query_set = query_set.filter(**{'knowledge_id': self.data.get("knowledge_id")}) tag_ids = self.data.get('tag_ids') no_tag = self.data.get('no_tag') tag_exclude = self.data.get('tag_exclude') if 'name' in self.data and self.data.get('name') is not None: query_set = query_set.filter(**{'name__icontains': self.data.get('name')}) if 'hit_handling_method' in self.data and self.data.get('hit_handling_method') not in [None, '']: query_set = query_set.filter(**{'hit_handling_method': self.data.get('hit_handling_method')}) if 'is_active' in self.data and self.data.get('is_active') is not None: query_set = query_set.filter(**{'is_active': self.data.get('is_active')}) if no_tag and tag_ids: matched_doc_ids = QuerySet(DocumentTag).filter(tag_id__in=tag_ids).values_list('document_id', flat=True) tagged_doc_ids = QuerySet(DocumentTag).values_list('document_id', flat=True) query_set = query_set.filter( Q(id__in=matched_doc_ids) | ~Q(id__in=tagged_doc_ids) ) elif no_tag: tagged_doc_ids = QuerySet(DocumentTag).values_list('document_id', flat=True) query_set = query_set.exclude(id__in=tagged_doc_ids) elif tag_ids: matched_doc_ids = QuerySet(DocumentTag).filter(tag_id__in=tag_ids).values_list('document_id', flat=True) if tag_exclude: query_set = query_set.exclude(id__in=matched_doc_ids) else: query_set = query_set.filter(id__in=matched_doc_ids) if 'status' in self.data and self.data.get('status') is not None: task_type = self.data.get('task_type') status = self.data.get('status') if task_type is not None: query_set = query_set.annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType(task_type).value, 1), ).filter( task_type_status=State(status).value ).values('id') else: if status != State.SUCCESS.value: query_set = query_set.filter(status__icontains=status) else: query_set = query_set.filter(status__iregex='^[2n]*$') if 'tag' in self.data and self.data.get('tag') not in [None, '']: tag_name = self.data.get('tag') document_id_list = QuerySet(DocumentTag).filter( Q(tag__key__icontains=tag_name) | Q(tag__value__icontains=tag_name) ).values_list('document_id', flat=True) query_set = query_set.filter(id__in=document_id_list) order_by = self.data.get('order_by', '') order_by_query_set = QuerySet(model=get_dynamics_model( {'char_length': models.CharField(), 'paragraph_count': models.IntegerField(), "update_time": models.IntegerField(), 'create_time': models.DateTimeField()})) if order_by: order_by_query_set = order_by_query_set.order_by(order_by) else: order_by_query_set = order_by_query_set.order_by('-create_time', 'id') return { 'document_custom_sql': query_set, 'order_by_query': order_by_query_set } def list(self): self.is_valid(raise_exception=True) query_set = self.get_query_set() return native_search(query_set, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_document.sql'))) def page(self, current_page, page_size): self.is_valid(raise_exception=True) query_set = self.get_query_set() return native_page_search(current_page, page_size, query_set, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_document.sql'))) class Sync(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=False, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) document_id = self.data.get('document_id') first = QuerySet(Document).filter(id=document_id).first() if first is None: raise AppApiException(500, _('document id not exist')) if first.type != KnowledgeType.WEB: raise AppApiException(500, _('Synchronization is only supported for web site types')) @transaction.atomic def sync(self, with_valid=True, with_embedding=True): if with_valid: self.is_valid(raise_exception=True) document_id = self.data.get('document_id') document = QuerySet(Document).filter(id=document_id).first() state = State.SUCCESS if document.type != KnowledgeType.WEB: return True try: ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.SYNC, State.PENDING) ListenerManagement.get_aggregation_document_status(document_id)() source_url = document.meta.get('source_url') selector_list = document.meta.get('selector').split( " ") if 'selector' in document.meta and document.meta.get('selector') is not None else [] result = Fork(source_url, selector_list).fork() if result.status == 200: # 删除段落 QuerySet(model=Paragraph).filter(document_id=document_id).delete() # 删除问题 QuerySet(model=ProblemParagraphMapping).filter(document_id=document_id).delete() delete_problems_and_mappings([document_id]) # 删除向量库 delete_embedding_by_document(document_id) paragraphs = get_split_model('web.md').parse(result.content) char_length = reduce(lambda x, y: x + y, [len(p.get('content')) for p in paragraphs], 0) QuerySet(Document).filter(id=document_id).update(char_length=char_length) document_paragraph_model = DocumentSerializers.Create.get_paragraph_model(document, paragraphs) paragraph_model_list = document_paragraph_model.get('paragraph_model_list') problem_paragraph_object_list = document_paragraph_model.get('problem_paragraph_object_list') problem_model_list, problem_paragraph_mapping_list = ProblemParagraphManage( problem_paragraph_object_list, document.knowledge_id).to_problem_model_list() # 批量插入段落 if len(paragraph_model_list) > 0: max_position = Paragraph.objects.filter(document_id=document_id).aggregate( max_position=Max('position') )['max_position'] or 0 for i, paragraph in enumerate(paragraph_model_list): paragraph.position = max_position + i + 1 QuerySet(Paragraph).bulk_create(paragraph_model_list) # 批量插入问题 QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None # 插入关联问题 QuerySet(ProblemParagraphMapping).bulk_create(problem_paragraph_mapping_list) if len( problem_paragraph_mapping_list) > 0 else None # 向量化 if with_embedding: embedding_model_id = get_embedding_model_id_by_knowledge_id(document.knowledge_id) ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.PENDING) ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id=document_id), TaskType.EMBEDDING, State.PENDING) ListenerManagement.get_aggregation_document_status(document_id)() embedding_by_document.delay(document_id, embedding_model_id) else: state = State.FAILURE except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') state = State.FAILURE ListenerManagement.update_status( QuerySet(Document).filter(id=document_id), TaskType.SYNC, state ) ListenerManagement.update_status( QuerySet(Paragraph).filter(document_id=document_id), TaskType.SYNC, state ) ListenerManagement.get_aggregation_document_status(document_id)() return True class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_blank=True) document_id = serializers.UUIDField(required=True, label=_('document id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) document_id = self.data.get('document_id') if not QuerySet(Document).filter(id=document_id).exists(): raise AppApiException(500, _('document id not exist')) def export(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document = QuerySet(Document).filter(id=self.data.get("document_id")).first() paragraph_query_set = QuerySet(Paragraph).filter( document_id=self.data.get("document_id") ).order_by('position') paragraph_list = native_search( paragraph_query_set, get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql')) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(document_id=self.data.get("document_id")), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True) data_dict, document_dict = self.merge_problem(paragraph_list, problem_mapping_list, [document]) workbook = self.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/vnd.ms-excel') response['Content-Disposition'] = f'attachment; filename="data.xlsx"' workbook.save(response) return response def export_zip(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document = QuerySet(Document).filter(id=self.data.get("document_id")).first() paragraph_query_set = QuerySet(Paragraph).filter( document_id=self.data.get("document_id") ).order_by('position') paragraph_list = native_search( paragraph_query_set, get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql') ) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(document_id=self.data.get("document_id")), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True) data_dict, document_dict = self.merge_problem(paragraph_list, problem_mapping_list, [document]) res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list] workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = f'attachment; filename="{document.name.strip()}.zip"' zip_buffer = io.BytesIO() with TemporaryDirectory() as tempdir: knowledge_file = os.path.join(tempdir, 'document.xlsx') workbook.save(knowledge_file) for r in res: write_image(tempdir, r) zip_dir(tempdir, zip_buffer) response.write(zip_buffer.getvalue()) return response def download_source_file(self): self.is_valid(raise_exception=True) file = QuerySet(File).filter(source_id=self.data.get('document_id')).first() if not file: raise AppApiException(500, _('File not exist. Only manually uploaded documents are supported')) return FileSerializer.Operate(data={'id': file.id}).get(with_valid=True) def one(self, with_valid=False): self.is_valid(raise_exception=True) query_set = QuerySet(model=Document) query_set = query_set.filter(**{'id': self.data.get("document_id")}) return native_search({ 'document_custom_sql': query_set, 'order_by_query': QuerySet(Document).order_by('-create_time', 'id') }, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_document.sql')), with_search_one=True) def edit(self, instance: Dict, with_valid=False): if with_valid: self.is_valid(raise_exception=True) _document = QuerySet(Document).get(id=self.data.get("document_id")) if with_valid: DocumentEditInstanceSerializer(data=instance).is_valid(document=_document) update_keys = ['name', 'is_active', 'hit_handling_method', 'directly_return_similarity', 'meta'] for update_key in update_keys: if update_key in instance and instance.get(update_key) is not None: _document.__setattr__(update_key, instance.get(update_key)) _document.save() return self.one() def cancel(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) CancelInstanceSerializer(data=instance).is_valid() document_id = self.data.get("document_id") ListenerManagement.update_status( QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1), ).filter( task_type_status__in=[State.PENDING.value, State.STARTED.value] ).filter( document_id=document_id ).values('id'), TaskType(instance.get('type')), State.REVOKE ) ListenerManagement.update_status( QuerySet(Document).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1), ).filter( task_type_status__in=[State.PENDING.value, State.STARTED.value] ).filter( id=document_id ).values('id'), TaskType(instance.get('type')), State.REVOKE ) return True @transaction.atomic def delete(self): self.is_valid(raise_exception=True) document_id = self.data.get("document_id") source_file_ids = [ doc['meta'].get( 'source_file_id' ) for doc in Document.objects.filter(id=document_id).values("meta") ] QuerySet(File).filter(id__in=source_file_ids).delete() QuerySet(File).filter(source_id=document_id, source_type=FileSourceType.DOCUMENT).delete() paragraph_ids = QuerySet(model=Paragraph).filter(document_id=document_id).values_list("id", flat=True) # 删除问题 delete_problems_and_mappings(paragraph_ids) # 删除段落 QuerySet(model=Paragraph).filter(document_id=document_id).delete() # 删除向量库 delete_embedding_by_document(document_id) QuerySet(model=DocumentTag).filter(document_id=document_id).delete() QuerySet(model=Document).filter(id=document_id).delete() return True def refresh(self, state_list=None, with_valid=True): if state_list is None: state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value, State.REVOKE.value, State.REVOKED.value, State.IGNORED.value] if with_valid: self.is_valid(raise_exception=True) knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first() embedding_model_id = knowledge.embedding_model_id knowledge_user_id = knowledge.user_id embedding_model = QuerySet(Model).filter(id=embedding_model_id).first() if embedding_model is None: raise AppApiException(500, _('Model does not exist')) document_id = self.data.get("document_id") ListenerManagement.update_status( QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.PENDING ) ListenerManagement.update_status( QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType.EMBEDDING.value, 1), ).filter(task_type_status__in=state_list, document_id=document_id).values('id'), TaskType.EMBEDDING, State.PENDING ) ListenerManagement.get_aggregation_document_status(document_id)() try: embedding_by_document.delay(document_id, embedding_model_id, state_list) except AlreadyQueued as e: raise AppApiException(500, _('The task is being executed, please do not send it repeatedly.')) @staticmethod def get_workbook(data_dict, document_dict): # 创建工作簿对象 workbook = openpyxl.Workbook() workbook.remove(workbook.active) if len(data_dict.keys()) == 0: data_dict['sheet'] = [] for sheet_id in data_dict: # 添加工作表 worksheet = workbook.create_sheet(document_dict.get(sheet_id)) data = [ [gettext('Section title (optional)'), gettext('Section content (required, question answer, no more than 4096 characters)'), gettext('Question (optional, one per line in the cell)')], *data_dict.get(sheet_id, []) ] # 写入数据到工作表 for row_idx, row in enumerate(data): for col_idx, col in enumerate(row): cell = worksheet.cell(row=row_idx + 1, column=col_idx + 1) if isinstance(col, str): col = re.sub(ILLEGAL_CHARACTERS_RE, '', col) if col.startswith(('=', '+', '-', '@')): col = '\ufeff' + col cell.value = col # 创建HttpResponse对象返回Excel文件 return workbook @staticmethod def merge_problem(paragraph_list: List[Dict], problem_mapping_list: List[Dict], document_list): result = {} document_dict = {} for paragraph in paragraph_list: problem_list = [problem_mapping.get('content') for problem_mapping in problem_mapping_list if problem_mapping.get('paragraph_id') == paragraph.get('id')] document_sheet = result.get(paragraph.get('document_id')) document_name = DocumentSerializers.Operate.reset_document_name(paragraph.get('document_name')) d = document_dict.get(document_name) if d is None: document_dict[document_name] = {paragraph.get('document_id')} else: d.add(paragraph.get('document_id')) if document_sheet is None: result[paragraph.get('document_id')] = [[paragraph.get('title'), paragraph.get('content'), '\n'.join(problem_list)]] else: document_sheet.append([paragraph.get('title'), paragraph.get('content'), '\n'.join(problem_list)]) for document in document_list: if document.id not in result: document_name = DocumentSerializers.Operate.reset_document_name(document.name) result[document.id] = [[]] d = document_dict.get(document_name) if d is None: document_dict[document_name] = {document.id} else: d.add(document.id) result_document_dict = {} for d_name in document_dict: for index, d_id in enumerate(document_dict.get(d_name)): result_document_dict[d_id] = d_name if index == 0 else d_name + str(index) return result, result_document_dict @staticmethod def reset_document_name(document_name): if document_name is not None: document_name = document_name.strip()[0:29] if document_name is None or not Utils.valid_sheet_name(document_name): return "Sheet" return document_name.strip() class Create(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True) knowledge_id = serializers.UUIDField(required=True, label=_('document id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if not QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).exists(): raise AppApiException(10000, _('knowledge id not exist')) return True @staticmethod def post_embedding(result, document_id, knowledge_id): DocumentSerializers.Operate( data={'knowledge_id': knowledge_id, 'document_id': document_id}).refresh() return result @post(post_function=post_embedding) @transaction.atomic def save(self, instance: Dict, with_valid=True, **kwargs): if with_valid: DocumentInstanceSerializer(data=instance).is_valid(raise_exception=True) self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') document_paragraph_model = self.get_document_paragraph_model(knowledge_id, instance) document_model = document_paragraph_model.get('document') paragraph_model_list = document_paragraph_model.get('paragraph_model_list') problem_paragraph_object_list = document_paragraph_model.get('problem_paragraph_object_list') problem_model_list, problem_paragraph_mapping_list = ( ProblemParagraphManage(problem_paragraph_object_list, knowledge_id).to_problem_model_list()) # 插入文档 document_model.save() # 批量插入段落 if len(paragraph_model_list) > 0: max_position = Paragraph.objects.filter(document_id=document_model.id).aggregate( max_position=Max('position') )['max_position'] or 0 for i, paragraph in enumerate(paragraph_model_list): paragraph.position = max_position + i + 1 QuerySet(Paragraph).bulk_create(paragraph_model_list) # 批量插入问题 QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None # 批量插入关联问题 QuerySet(ProblemParagraphMapping).bulk_create( problem_paragraph_mapping_list ) if len(problem_paragraph_mapping_list) > 0 else None document_id = str(document_model.id) return (DocumentSerializers.Operate( data={'knowledge_id': knowledge_id, 'document_id': document_id} ).one(with_valid=True), document_id, knowledge_id) @staticmethod def get_paragraph_model(document_model, paragraph_list: List): knowledge_id = document_model.knowledge_id paragraph_model_dict_list = [ ParagraphSerializers.Create( data={ 'knowledge_id': knowledge_id, 'document_id': str(document_model.id) }).get_paragraph_problem_model(knowledge_id, document_model.id, paragraph) for paragraph in paragraph_list] paragraph_model_list = [] problem_paragraph_object_list = [] for paragraphs in paragraph_model_dict_list: paragraph = paragraphs.get('paragraph') for problem_model in paragraphs.get('problem_paragraph_object_list'): problem_paragraph_object_list.append(problem_model) paragraph_model_list.append(paragraph) return { 'document': document_model, 'paragraph_model_list': paragraph_model_list, 'problem_paragraph_object_list': problem_paragraph_object_list } @staticmethod def get_document_paragraph_model(knowledge_id, instance: Dict): source_meta = {'source_file_id': instance.get('source_file_id')} if instance.get('source_file_id') else {} meta = {**instance.get('meta'), **source_meta} if instance.get('meta') is not None else source_meta meta = {**convert_uuid_to_str(meta), 'allow_download': True} document_model = Document( **{ 'knowledge_id': knowledge_id, 'id': uuid.uuid7(), 'name': instance.get('name'), 'char_length': reduce( lambda x, y: x + y, [len(p.get('content')) for p in instance.get('paragraphs', [])], 0), 'meta': meta, 'type': instance.get('type') if instance.get('type') is not None else KnowledgeType.BASE }) return DocumentSerializers.Create.get_paragraph_model( document_model, instance.get('paragraphs') if 'paragraphs' in instance else [] ) def save_web(self, instance: Dict, with_valid=True): if with_valid: DocumentWebInstanceSerializer(data=instance).is_valid(raise_exception=True) self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') source_url_list = instance.get('source_url_list') selector = instance.get('selector') sync_web_document.delay(knowledge_id, source_url_list, selector) def save_qa(self, instance: Dict, with_valid=True): if with_valid: DocumentInstanceQASerializer(data=instance).is_valid(raise_exception=True) self.is_valid(raise_exception=True) file_list = instance.get('file_list') document_list = flat_map([self.parse_qa_file(file) for file in file_list]) return DocumentSerializers.Batch(data={ 'knowledge_id': self.data.get('knowledge_id'), 'workspace_id': self.data.get('workspace_id') }).batch_save(document_list) def save_table(self, instance: Dict, with_valid=True): if with_valid: DocumentInstanceTableSerializer(data=instance).is_valid(raise_exception=True) self.is_valid(raise_exception=True) file_list = instance.get('file_list') document_list = flat_map([self.parse_table_file(file) for file in file_list]) return DocumentSerializers.Batch(data={ 'knowledge_id': self.data.get('knowledge_id'), 'workspace_id': self.data.get('workspace_id') }).batch_save(document_list) def parse_qa_file(self, file): # 保存源文件 source_file_id = uuid.uuid7() source_file = File( id=source_file_id, file_name=file.name, source_type=FileSourceType.KNOWLEDGE, source_id=self.data.get('knowledge_id'), meta={} ) source_file.save(file.read()) file.seek(0) get_buffer = FileBufferHandle().get_buffer for parse_qa_handle in parse_qa_handle_list: if parse_qa_handle.support(file, get_buffer): documents = parse_qa_handle.handle(file, get_buffer, self.save_image) for doc in documents: doc['source_file_id'] = source_file_id return documents raise AppApiException(500, _('Unsupported file format')) def parse_table_file(self, file): # 保存源文件 source_file_id = uuid.uuid7() source_file = File( id=source_file_id, file_name=file.name, source_type=FileSourceType.KNOWLEDGE, source_id=self.data.get('knowledge_id'), meta={} ) source_file.save(file.read()) file.seek(0) get_buffer = FileBufferHandle().get_buffer for parse_table_handle in parse_table_handle_list: if parse_table_handle.support(file, get_buffer): documents = parse_table_handle.handle(file, get_buffer, self.save_image) for doc in documents: doc['source_file_id'] = source_file_id return documents raise AppApiException(500, _('Unsupported file format')) def save_image(self, image_list): if image_list is not None and len(image_list) > 0: exist_image_list = [str(i.get('id')) for i in QuerySet(File).filter(id__in=[i.id for i in image_list]).values('id')] save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))] save_image_list = list({img.id: img for img in save_image_list}.values()) # save image for file in save_image_list: file_bytes = file.meta.pop('content') file.meta['knowledge_id'] = self.data.get('knowledge_id') file.source_type = FileSourceType.KNOWLEDGE file.source_id = self.data.get('knowledge_id') file.save(file_bytes) class Split(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, instance=None, raise_exception=True): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) files = instance.get('file') knowledge = Knowledge.objects.filter(id=self.data.get('knowledge_id')).first() for f in files: if f.size > 1024 * 1024 * knowledge.file_size_limit: raise AppApiException(500, _( 'The maximum size of the uploaded file cannot exceed {}MB' ).format(knowledge.file_size_limit)) def parse(self, instance): self.is_valid(instance=instance, raise_exception=True) DocumentSplitRequest(data=instance).is_valid(raise_exception=True) file_list = instance.get("file") return reduce( lambda x, y: [*x, *y], [self.file_to_paragraph( f, instance.get("patterns", None), instance.get("with_filter", None), instance.get("limit", 4096) ) for f in file_list], [] ) def save_image(self, image_list): if image_list is not None and len(image_list) > 0: exist_image_list = [str(i.get('id')) for i in QuerySet(File).filter(id__in=[i.id for i in image_list]).values('id')] save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))] save_image_list = list({img.id: img for img in save_image_list}.values()) # save image for file in save_image_list: file_bytes = file.meta.pop('content') file.meta['knowledge_id'] = self.data.get('knowledge_id') file.source_type = FileSourceType.KNOWLEDGE file.source_id = self.data.get('knowledge_id') file.save(file_bytes) def file_to_paragraph(self, file, pattern_list: List, with_filter: bool, limit: int): # 保存源文件 file_id = uuid.uuid7() raw_file = File( id=file_id, file_name=file.name, file_size=file.size, source_type=FileSourceType.KNOWLEDGE, source_id=self.data.get('knowledge_id'), ) raw_file.save(file.read()) file.seek(0) get_buffer = FileBufferHandle().get_buffer for split_handle in split_handles: if split_handle.support(file, get_buffer): result = split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, self.save_image) if isinstance(result, list): for item in result: item['source_file_id'] = file_id return result result['source_file_id'] = file_id return [result] result = default_split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, self.save_image) if isinstance(result, list): for item in result: item['source_file_id'] = file_id return result result['source_file_id'] = file_id return [result] class SplitPattern(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) @staticmethod def list(): return [ {'key': "#", 'value': '(?<=^)# .*|(?<=\\n)# .*'}, {'key': '##', 'value': '(?<=\\n)(? 0 else None # 批量插入段落 if len(paragraph_model_list) > 0: for document in document_model_list: max_position = Paragraph.objects.filter(document_id=document.id).aggregate( max_position=Max('position') )['max_position'] or 0 sub_list = [p for p in paragraph_model_list if p.document_id == document.id] for i, paragraph in enumerate(sub_list): paragraph.position = max_position + i + 1 QuerySet(Paragraph).bulk_create(sub_list if len(sub_list) > 0 else []) # 批量插入问题 bulk_create_in_batches(Problem, problem_model_list, batch_size=1000) # 批量插入关联问题 bulk_create_in_batches(ProblemParagraphMapping, problem_paragraph_mapping_list, batch_size=1000) # 查询文档 query_set = QuerySet(model=Document) if len(document_model_list) == 0: return [], knowledge_id, workspace_id query_set = query_set.filter(**{'id__in': [d.id for d in document_model_list]}) return native_search( { 'document_custom_sql': query_set, 'order_by_query': QuerySet(Document).order_by('-create_time', 'id') }, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_document.sql') ), with_search_one=False ), knowledge_id, workspace_id def batch_sync(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) self.is_valid(raise_exception=True) # 异步同步 work_thread_pool.submit( lambda doc_ids: [ DocumentSerializers.Sync(data={ 'document_id': doc_id, 'knowledge_id': self.data.get('knowledge_id'), 'workspace_id': self.data.get('workspace_id') }).sync() for doc_id in doc_ids ], instance.get('id_list') ) return True @transaction.atomic def batch_delete(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) self.is_valid(raise_exception=True) document_id_list = instance.get("id_list") source_file_ids = [doc['meta'].get('source_file_id') for doc in Document.objects.filter(id__in=document_id_list).values("meta")] QuerySet(File).filter(id__in=source_file_ids).delete() QuerySet(Document).filter(id__in=document_id_list).delete() QuerySet(DocumentTag).filter(document_id__in=document_id_list).delete() paragraph_ids = QuerySet(Paragraph).filter(document_id__in=document_id_list).values_list("id", flat=True) # 删除问题关系 delete_problems_and_mappings(paragraph_ids) # 删除段落 QuerySet(Paragraph).filter(document_id__in=document_id_list).delete() # 删除向量库 delete_embedding_by_document_list(document_id_list) return True def batch_cancel(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) BatchCancelInstanceSerializer(data=instance).is_valid(raise_exception=True) document_id_list = instance.get("id_list") ListenerManagement.update_status( QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1), ).filter( task_type_status__in=[State.PENDING.value, State.STARTED.value] ).filter( document_id__in=document_id_list ).values('id'), TaskType(instance.get('type')), State.REVOKE ) ListenerManagement.update_status( QuerySet(Document).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1), ).filter( task_type_status__in=[State.PENDING.value, State.STARTED.value] ).filter( id__in=document_id_list ).values('id'), TaskType(instance.get('type')), State.REVOKE ) def batch_edit_hit_handling(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) hit_handling_method = instance.get('hit_handling_method') if hit_handling_method is None: raise AppApiException(500, _('Hit handling method is required')) if hit_handling_method != 'optimization' and hit_handling_method != 'directly_return': raise AppApiException(500, _('The hit processing method must be directly_return|optimization')) self.is_valid(raise_exception=True) document_id_list = instance.get("id_list") hit_handling_method = instance.get('hit_handling_method') directly_return_similarity = instance.get('directly_return_similarity') update_dict = {'hit_handling_method': hit_handling_method} if directly_return_similarity is not None: update_dict['directly_return_similarity'] = directly_return_similarity QuerySet(Document).filter(id__in=document_id_list).update(**update_dict) allow_download = instance.get('allow_download') if allow_download is not None: # 我需要修改meta meta是存在Document的字段 是一个json字段 但是allow_download可能不存在 Document.objects.filter(id__in=document_id_list).update( meta=Func( F("meta"), Value(["allow_download"]), Value(json.dumps(allow_download)), # 转成 "true"/"false" Value(True), # create_missing = true function="jsonb_set", output_field=JSONField(), ) ) def batch_refresh(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document_id_list = instance.get("id_list") state_list = instance.get("state_list") knowledge_id = self.data.get('knowledge_id') for document_id in document_id_list: try: DocumentSerializers.Operate( data={'knowledge_id': knowledge_id, 'document_id': document_id}).refresh(state_list) except AlreadyQueued as e: pass def batch_add_tag(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document_id_list = instance.get("document_ids") tag_id_list = instance.get("tag_ids") # 批量查询已存在的标签关联关系 existing_relations = { (str(doc_id), str(tag_id)) for doc_id, tag_id in QuerySet(DocumentTag).filter( document_id__in=document_id_list, tag_id__in=tag_id_list ).values_list('document_id', 'tag_id') } new_relations = [] for doc_id in document_id_list: for tag_id in tag_id_list: relation_key = (str(doc_id), str(tag_id)) # 既检查数据库中已存在的,也检查本次即将创建的 if relation_key not in existing_relations: new_relations.append(DocumentTag( id=uuid.uuid7(), document_id=doc_id, tag_id=tag_id, )) existing_relations.add(relation_key) if new_relations: QuerySet(DocumentTag).bulk_create(new_relations) def batch_export(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) self.is_valid(raise_exception=True) document_ids = instance.get("id_list") document_list = QuerySet(Document).filter(id__in=document_ids) paragraph_list = native_search( QuerySet(Paragraph).filter(document_id__in=document_ids), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql') ) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(document_id__in=document_ids), get_file_content(os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True ) data_dict, document_dict = DocumentSerializers.Operate.merge_problem( paragraph_list, problem_mapping_list, document_list ) workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/vnd.ms-excel') workbook.save(response) return response def batch_export_zip(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) self.is_valid(raise_exception=True) knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first() document_ids = instance.get("id_list") document_list = QuerySet(Document).filter(id__in=document_ids) paragraph_list = native_search( QuerySet(Paragraph).filter(document_id__in=document_ids), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql') ) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(document_id__in=document_ids), get_file_content(os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True ) data_dict, document_dict = DocumentSerializers.Operate.merge_problem( paragraph_list, problem_mapping_list, document_list ) res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list] workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/zip') zip_buffer = io.BytesIO() with TemporaryDirectory() as tempdir: knowledge_file = os.path.join(tempdir, f'{knowledge.name}.xlsx') workbook.save(knowledge_file) for r in res: write_image(tempdir, r) zip_dir(tempdir, zip_buffer) response.write(zip_buffer.getvalue()) return response class BatchGenerateRelated(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def batch_generate_related(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document_id_list = instance.get("document_id_list") model_id = instance.get("model_id") prompt = instance.get("prompt") model_params_setting = instance.get("model_params_setting") state_list = instance.get('state_list') ListenerManagement.update_status( QuerySet(Document).filter(id__in=document_id_list), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.update_status( QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value, 1), ).filter( task_type_status__in=state_list, document_id__in=document_id_list ) .values('id'), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.get_aggregation_document_status_by_query_set( QuerySet(Document).filter(id__in=document_id_list))() try: for document_id in document_id_list: generate_related_by_document_id.delay( document_id, model_id, model_params_setting, prompt, state_list ) except AlreadyQueued as e: pass class Tags(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Document).filter( id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id') ).exists(): raise AppApiException(500, _('Document id does not exist')) def list(self): self.is_valid(raise_exception=True) tag_ids = QuerySet(DocumentTag).filter( document_id=self.data.get('document_id') ).values_list('tag_id', flat=True) if self.data.get('name'): tag_ids = QuerySet(Tag).filter( knowledge_id=self.data.get('knowledge_id'), id__in=tag_ids, ).filter( Q(key__icontains=self.data.get('name')) | Q(value__icontains=self.data.get('name')) ).values_list('id', flat=True) # 获取所有标签,按创建时间排序保持稳定顺序 tags = QuerySet(Tag).filter( knowledge_id=self.data.get('knowledge_id'), id__in=tag_ids ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value') # 按key分组 grouped_tags = defaultdict(list) for tag in tags: grouped_tags[tag['key']].append({ 'id': tag['id'], 'value': tag['value'], 'create_time': tag['create_time'], 'update_time': tag['update_time'] }) # 转换为期望的格式,保持key的顺序 result = [] # 按key排序以确保结果顺序一致 for key in sorted(grouped_tags.keys()): values = grouped_tags[key] # 按创建时间对values进行排序 values.sort(key=lambda x: x['create_time']) result.append({ 'key': key, 'values': values, }) return result class AddTags(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) tag_ids = serializers.ListField( required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id')) ) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Document).filter( id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id') ).exists(): raise AppApiException(500, _('Document id does not exist')) def add_tags(self): self.is_valid(raise_exception=True) document_id = self.data.get('document_id') tag_ids = self.data.get('tag_ids') existing_tag_ids = set( str(tag_id) for tag_id in QuerySet(DocumentTag).filter( document_id=document_id, tag_id__in=tag_ids ).values_list('tag_id', flat=True) ) new_tags = [ DocumentTag( id=uuid.uuid7(), document_id=document_id, tag_id=tag_id ) for tag_id in set(tag_ids) if tag_id not in existing_tag_ids ] if new_tags: QuerySet(DocumentTag).bulk_create(new_tags) class DeleteTags(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) tag_ids = serializers.ListField( required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id')) ) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Document).filter( id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id') ).exists(): raise AppApiException(500, _('Document id does not exist')) def delete_tags(self): self.is_valid(raise_exception=True) document_id = self.data.get('document_id') tag_ids = self.data.get('tag_ids') QuerySet(DocumentTag).filter( document_id=document_id, tag_id__in=tag_ids ).delete() class DeleteDocsTag(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) tag_id = serializers.UUIDField(required=True, label=_('tag id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Tag).filter( id=self.data.get('tag_id'), knowledge_id=self.data.get('knowledge_id') ).exists(): raise AppApiException(500, _('Tag id does not exist')) def batch_delete_docs_tag(self, instance, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True) self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') tag_id = self.data.get('tag_id') doc_id_list = instance.get("id_list") valid_doc_count = Document.objects.filter(id__in=doc_id_list, knowledge_id=knowledge_id).count() if valid_doc_count != len(doc_id_list): raise AppApiException(500, _('Document id does not belong to current knowledge')) DocumentTag.objects.filter(document_id__in=doc_id_list, tag_id=tag_id).delete() return True class ReplaceSourceFile(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) file = UploadedFileField(required=True, label=_("file")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Document).filter( id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id') ).exists(): raise AppApiException(500, _('Document id does not exist')) @transaction.atomic def replace(self): self.is_valid(raise_exception=True) file = self.data.get('file') source_file = QuerySet(File).filter(source_id=self.data.get('document_id')).first() if not source_file: # 不存在手动关联一个文档 new_source_file_id = uuid.uuid7() new_source_file = File( id=new_source_file_id, file_name=file.name, source_type=FileSourceType.DOCUMENT, source_id=self.data.get('document_id'), ) new_source_file.save(file.read()) # 更新Document的meta字段 QuerySet(Document).filter(id=self.data.get('document_id')).update( meta=Func( F("meta"), Value(["source_file_id"]), Value(json.dumps(str(new_source_file_id))), Value(True), # create_missing = true function="jsonb_set", output_field=JSONField(), ) ) else: # 获取原文件的sha256_hash original_hash = source_file.sha256_hash # 读取新文件内容 file_content = file.read() QuerySet(File).filter( sha256_hash=original_hash, source_id__in=[self.data.get('knowledge_id'), self.data.get('document_id')] ).update(file_name=file.name) # 查找所有具有相同sha256_hash的文件 files_to_update = QuerySet(File).filter( sha256_hash=original_hash, source_id__in=[self.data.get('knowledge_id'), self.data.get('document_id')] ) # 更新所有相同hash的文件 for file_obj in files_to_update: file_obj.save(file_content) return True class FileBufferHandle: buffer = None def get_buffer(self, file): if self.buffer is None: self.buffer = file.read() return self.buffer ================================================ FILE: apps/knowledge/serializers/knowledge.py ================================================ import io import json import os import re import tempfile import traceback import zipfile from collections import defaultdict from functools import reduce from tempfile import TemporaryDirectory from typing import Dict, List import requests import uuid_utils.compat as uuid from celery_once import AlreadyQueued from django.core import validators from django.db import transaction, models from django.db.models import QuerySet from django.db.models.functions import Reverse, Substr from django.db.models.query_utils import Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.config.embedding_config import VectorStore from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search, get_dynamics_model, native_page_search from common.db.sql_execute import select_list from common.event.listener_manage import ListenerManagement from common.exception.app_exception import AppApiException from common.utils.common import post, get_file_content, parse_image from common.utils.fork import Fork, ChildLink from common.utils.logger import maxkb_logger from common.utils.split_model import get_split_model from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType, Document, Paragraph, Problem, \ ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag, KnowledgeWorkflow from knowledge.serializers.common import ProblemParagraphManage, drop_knowledge_index, \ get_embedding_model_id_by_knowledge_id, MetaSerializer, \ GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph, write_image, zip_dir, \ update_resource_mapping_by_knowledge from knowledge.serializers.document import DocumentSerializers from knowledge.task.embedding import embedding_by_knowledge, delete_embedding_by_knowledge from knowledge.task.generate import generate_related_by_knowledge_id from knowledge.task.sync import sync_web_knowledge, sync_replace_web_knowledge from maxkb.conf import PROJECT_DIR from models_provider.models import Model from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType from system_manage.models.resource_mapping import ResourceMapping from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from users.serializers.user import is_workspace_manage class KnowledgeModelSerializer(serializers.ModelSerializer): class Meta: model = Knowledge fields = ['id', 'name', 'desc', 'meta', 'folder_id', 'type', 'workspace_id', 'create_time', 'update_time', 'file_size_limit', 'file_count_limit', 'embedding_model_id'] class KnowledgeBaseCreateRequest(serializers.Serializer): name = serializers.CharField(required=True, label=_('knowledge name')) folder_id = serializers.CharField(required=True, label=_('folder id')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description')) embedding_model_id = serializers.CharField(required=True, label=_('knowledge embedding')) class KnowledgeWebCreateRequest(serializers.Serializer): name = serializers.CharField(required=True, label=_('knowledge name')) folder_id = serializers.CharField(required=True, label=_('folder id')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description')) embedding_model_id = serializers.CharField(required=True, label=_('knowledge embedding')) source_url = serializers.CharField(required=True, label=_('source url')) selector = serializers.CharField(required=False, label=_('knowledge selector'), allow_null=True, allow_blank=True) class KnowledgeEditRequest(serializers.Serializer): name = serializers.CharField(required=False, max_length=64, min_length=1, label=_('knowledge name')) desc = serializers.CharField(required=False, max_length=256, min_length=1, label=_('knowledge description')) meta = serializers.DictField(required=False) application_id_list = serializers.ListSerializer( required=False, child=serializers.UUIDField(required=True, label=_('application id')), label=_('application id list') ) file_size_limit = serializers.IntegerField(required=False, label=_('file size limit')) file_count_limit = serializers.IntegerField(required=False, label=_('file count limit')) @staticmethod def get_knowledge_meta_valid_map(): knowledge_meta_valid_map = { KnowledgeType.BASE: MetaSerializer.BaseMeta, KnowledgeType.WEB: MetaSerializer.WebMeta } return knowledge_meta_valid_map def is_valid(self, *, knowledge: Knowledge = None): super().is_valid(raise_exception=True) if 'meta' in self.data and self.data.get('meta') is not None: knowledge_meta_valid_map = self.get_knowledge_meta_valid_map() valid_class = knowledge_meta_valid_map.get(knowledge.type) valid_class(data=self.data.get('meta')).is_valid(raise_exception=True) class HitTestSerializer(serializers.Serializer): query_text = serializers.CharField(required=True, label=_('query text')) top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1, label=_("top number")) similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_('similarity')) search_mode = serializers.CharField(required=True, label=_('search mode'), validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_('The type only supports embedding|keywords|blend'), code=500) ]) class KnowledgeSerializer(serializers.Serializer): class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True) folder_id = serializers.CharField(required=False, label=_('folder id'), allow_null=True) name = serializers.CharField(required=False, label=_('knowledge name'), allow_null=True, allow_blank=True, max_length=64, min_length=1) desc = serializers.CharField(required=False, label=_('knowledge description'), allow_null=True, allow_blank=True, max_length=256, min_length=1) user_id = serializers.UUIDField(required=False, label=_('user id'), allow_null=True) scope = serializers.CharField(required=False, label=_('knowledge scope'), allow_null=True) create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True) @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def get_query_set(self, workspace_manage, is_x_pack_ee): self.is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") query_set_dict = {} query_set = QuerySet(model=get_dynamics_model({ 'temp.name': models.CharField(), 'temp.desc': models.CharField(), "document_temp.char_length": models.IntegerField(), 'temp.create_time': models.DateTimeField(), 'temp.user_id': models.CharField(), 'temp.workspace_id': models.CharField(), 'temp.folder_id': models.CharField(), 'temp.id': models.CharField(), 'temp.scope': models.CharField(), })) folder_query_set = QuerySet(KnowledgeFolder) if "desc" in self.data and self.data.get('desc') is not None: query_set = query_set.filter(**{'temp.desc__icontains': self.data.get("desc")}) folder_query_set = folder_query_set.filter(**{'desc__icontains': self.data.get("desc")}) if "name" in self.data and self.data.get('name') is not None: query_set = query_set.filter(**{'temp.name__icontains': self.data.get("name")}) folder_query_set = folder_query_set.filter(**{'name__icontains': self.data.get("name")}) if "workspace_id" in self.data and self.data.get('workspace_id') is not None: query_set = query_set.filter(**{'temp.workspace_id': self.data.get("workspace_id")}) folder_query_set = folder_query_set.filter(**{'workspace_id': self.data.get("workspace_id")}) if "folder_id" in self.data and self.data.get('folder_id') is not None and self.data.get( 'workspace_id') != self.data.get('folder_id'): query_set = query_set.filter(**{'temp.folder_id': self.data.get("folder_id")}) folder_query_set = folder_query_set.filter(**{'parent_id': self.data.get("folder_id")}) if "scope" in self.data and self.data.get('scope') is not None: query_set = query_set.filter(**{'temp.scope': self.data.get("scope")}) if "create_user" in self.data and self.data.get('create_user') is not None: query_set = query_set.filter(**{'temp.user_id': self.data.get("create_user")}) query_set = query_set.order_by("-temp.create_time", "temp.id") query_set_dict['default_sql'] = query_set query_set_dict['knowledge_custom_sql'] = QuerySet(model=get_dynamics_model({ 'knowledge.workspace_id': models.CharField(), })).filter(**{'knowledge.workspace_id': workspace_id}) # query_set_dict['folder_query_set'] = folder_query_set if not workspace_manage: query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet( WorkspaceUserResourcePermission).filter( auth_target_type="KNOWLEDGE", workspace_id=workspace_id, user_id=self.data.get("user_id")) return query_set_dict def page(self, current_page: int, page_size: int): self.is_valid(raise_exception=True) folder_id = self.data.get('folder_id', self.data.get("workspace_id")) root = KnowledgeFolder.objects.filter(id=folder_id).first() if not root: raise serializers.ValidationError(_('Folder not found')) workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() result = native_page_search( current_page, page_size, self.get_query_set(workspace_manage, is_x_pack_ee), select_string=get_file_content( os.path.join( PROJECT_DIR, "apps", "knowledge", 'sql', 'list_knowledge.sql' if workspace_manage else ( 'list_knowledge_user_ee.sql' if is_x_pack_ee else 'list_knowledge_user.sql' ) ) ), post_records_handler=lambda r: r ) return ResourceMappingSerializer().get_resource_count(result) def list(self): self.is_valid(raise_exception=True) folder_id = self.data.get('folder_id') if not folder_id: folder_id = self.data.get('workspace_id') root = KnowledgeFolder.objects.filter(id=folder_id).first() if not root: raise serializers.ValidationError(_('Folder not found')) workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() return native_search( self.get_query_set(workspace_manage, is_x_pack_ee), select_string=get_file_content( os.path.join( PROJECT_DIR, "apps", "knowledge", 'sql', 'list_knowledge.sql' if workspace_manage else ( 'list_knowledge_user_ee.sql' if self.is_x_pack_ee() else 'list_knowledge_user.sql' ) ) ), ) class Operate(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def embedding(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() embedding_model_id = knowledge.embedding_model_id embedding_model = QuerySet(Model).filter(id=embedding_model_id).first() if embedding_model is None: raise AppApiException(500, _('Model does not exist')) ListenerManagement.update_status( QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id')), TaskType.EMBEDDING, State.PENDING ) ListenerManagement.update_status( QuerySet(Paragraph).filter(knowledge_id=self.data.get('knowledge_id')), TaskType.EMBEDDING, State.PENDING ) ListenerManagement.get_aggregation_document_status_by_knowledge_id(self.data.get('knowledge_id'))() embedding_model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id')) try: embedding_by_knowledge.delay(knowledge_id, embedding_model_id) except AlreadyQueued as e: raise AppApiException(500, _('Failed to send the vectorization task, please try again later!')) def generate_related(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) GenerateRelatedSerializer(data=instance).is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') model_id = instance.get("model_id") prompt = instance.get("prompt") model_params_setting = instance.get("model_params_setting") state_list = instance.get('state_list') ListenerManagement.update_status( QuerySet(Document).filter(knowledge_id=knowledge_id), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.update_status( QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value, 1), ).filter( task_type_status__in=state_list, knowledge_id=knowledge_id ).values('id'), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.get_aggregation_document_status_by_knowledge_id(knowledge_id)() try: generate_related_by_knowledge_id.delay(knowledge_id, model_id, model_params_setting, prompt, state_list) except AlreadyQueued as e: raise AppApiException(500, _('Failed to send the vectorization task, please try again later!')) def list_application(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) # knowledge = QuerySet(Knowledge).get(id=self.data.get("knowledge_id")) return select_list( get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_knowledge_application.sql') ), [ self.data.get('user_id'), ] ) @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def one(self): self.is_valid() workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() query_set_dict = { 'default_sql': QuerySet( model=get_dynamics_model({'temp.id': models.CharField()}) ).filter(**{'temp.id': self.data.get("knowledge_id")}), 'knowledge_custom_sql': QuerySet( model=get_dynamics_model({'knowledge.id': models.CharField()}) ).filter(**{'knowledge.id': self.data.get("knowledge_id")}), } if not workspace_manage: query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet( WorkspaceUserResourcePermission).filter( auth_target_type="KNOWLEDGE", workspace_id=self.data.get('workspace_id'), user_id=self.data.get("user_id") ) all_application_list = [str(adm.get('id')) for adm in self.list_application(with_valid=False)] knowledge_dict = native_search(query_set_dict, select_string=get_file_content( os.path.join( PROJECT_DIR, "apps", "knowledge", 'sql', 'list_knowledge.sql' if workspace_manage else ( 'list_knowledge_user_ee.sql' if is_x_pack_ee else 'list_knowledge_user.sql' ) ) ), with_search_one=True) workflow = {} if knowledge_dict.get('type') == 4: from knowledge.models import KnowledgeWorkflow k = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_dict.get('id')).first() if k: workflow['work_flow'] = k.work_flow workflow['is_publish'] = k.is_publish workflow['publish_time'] = k.publish_time return { **knowledge_dict, **workflow, 'meta': json.loads(knowledge_dict.get('meta', '{}')), 'application_id_list': list(filter( lambda application_id: all_application_list.__contains__(application_id), [ str( application_knowledge_mapping.source_id ) for application_knowledge_mapping in QuerySet(ResourceMapping).filter(source_type='APPLICATION', target_type='KNOWLEDGE', target_id=self.data.get('knowledge_id')) ] )) } @transaction.atomic def edit(self, instance: Dict, select_one=True): self.is_valid() knowledge = QuerySet(Knowledge).get(id=self.data.get("knowledge_id")) KnowledgeEditRequest(data=instance).is_valid(knowledge=knowledge) if 'embedding_model_id' in instance: knowledge.embedding_model_id = instance.get('embedding_model_id') if "name" in instance: knowledge.name = instance.get("name") if 'desc' in instance: knowledge.desc = instance.get("desc") if 'meta' in instance: knowledge.meta = instance.get('meta') if 'folder_id' in instance: knowledge.folder_id = instance.get('folder_id') if 'file_size_limit' in instance: knowledge.file_size_limit = instance.get('file_size_limit') if 'file_count_limit' in instance: knowledge.file_count_limit = instance.get('file_count_limit') knowledge.save() update_resource_mapping_by_knowledge(str(knowledge.id)) if select_one: return self.one() return None @transaction.atomic def delete(self): self.is_valid() knowledge = QuerySet(Knowledge).get(id=self.data.get("knowledge_id")) QuerySet(Document).filter(knowledge=knowledge).delete() QuerySet(ProblemParagraphMapping).filter(knowledge=knowledge).delete() QuerySet(Paragraph).filter(knowledge=knowledge).delete() QuerySet(Problem).filter(knowledge=knowledge).delete() QuerySet(WorkspaceUserResourcePermission).filter(target=knowledge.id).delete() drop_knowledge_index(knowledge_id=knowledge.id) knowledge.delete() File.objects.filter( source_id=knowledge.id, ).delete() QuerySet(ResourceMapping).filter( Q(target_id=self.data.get('knowledge_id')) | Q(source_id=self.data.get('knowledge_id')) ).delete() delete_embedding_by_knowledge(self.data.get('knowledge_id')) return True def export_excel(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) document_list = QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id')) paragraph_list = native_search( QuerySet(Paragraph).filter(knowledge_id=self.data.get("knowledge_id")), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql') ) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get("knowledge_id")), get_file_content(os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True ) data_dict, document_dict = DocumentSerializers.Operate.merge_problem( paragraph_list, problem_mapping_list, document_list ) workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/vnd.ms-excel') response['Content-Disposition'] = 'attachment; filename="knowledge.xlsx"' workbook.save(response) return response def export_zip(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge = QuerySet(Knowledge).filter(id=self.data.get("knowledge_id")).first() document_list = QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id')) paragraph_list = native_search( QuerySet(Paragraph).filter(knowledge_id=self.data.get("knowledge_id")), get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph_document_name.sql') ) ) problem_mapping_list = native_search( QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get("knowledge_id")), get_file_content(os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem_mapping.sql')), with_table_name=True ) data_dict, document_dict = DocumentSerializers.Operate.merge_problem( paragraph_list, problem_mapping_list, document_list ) res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list] workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = f'attachment; filename="{knowledge.name}.zip"' zip_buffer = io.BytesIO() with TemporaryDirectory() as tempdir: knowledge_file = os.path.join(tempdir, 'knowledge.xlsx') workbook.save(knowledge_file) for r in res: write_image(tempdir, r) zip_dir(tempdir, zip_buffer) response.write(zip_buffer.getvalue()) return response @staticmethod def merge_problem(paragraph_list: List[Dict], problem_mapping_list: List[Dict]): result = {} document_dict = {} for paragraph in paragraph_list: problem_list = [problem_mapping.get('content') for problem_mapping in problem_mapping_list if problem_mapping.get('paragraph_id') == paragraph.get('id')] document_sheet = result.get(paragraph.get('document_id')) d = document_dict.get(paragraph.get('document_name')) if d is None: document_dict[paragraph.get('document_name')] = {paragraph.get('document_id')} else: d.add(paragraph.get('document_id')) if document_sheet is None: result[paragraph.get('document_id')] = [[paragraph.get('title'), paragraph.get('content'), '\n'.join(problem_list)]] else: document_sheet.append([paragraph.get('title'), paragraph.get('content'), '\n'.join(problem_list)]) result_document_dict = {} for d_name in document_dict: for index, d_id in enumerate(document_dict.get(d_name)): result_document_dict[d_id] = d_name if index == 0 else d_name + str(index) return result, result_document_dict class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) scope = serializers.ChoiceField(required=False, label=_('scope'), default=KnowledgeScope.WORKSPACE, choices=KnowledgeScope.choices) @staticmethod def post_embedding_knowledge(document_list, knowledge_id): model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) embedding_by_knowledge.delay(knowledge_id, model_id) return document_list @post(post_function=post_embedding_knowledge) @transaction.atomic def save_base(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) KnowledgeBaseCreateRequest(data=instance).is_valid(raise_exception=True) folder_id = instance.get('folder_id', self.data.get('workspace_id')) knowledge_id = uuid.uuid7() knowledge = Knowledge( id=knowledge_id, name=instance.get('name'), workspace_id=self.data.get('workspace_id'), desc=instance.get('desc'), type=instance.get('type', KnowledgeType.BASE), user_id=self.data.get('user_id'), scope=self.data.get('scope', KnowledgeScope.WORKSPACE), folder_id=folder_id, embedding_model_id=instance.get('embedding_model_id'), meta=instance.get('meta', {}), ) document_model_list = [] paragraph_model_list = [] problem_paragraph_object_list = [] # 插入文档 for document in instance.get('documents') if 'documents' in instance else []: document_paragraph_dict_model = DocumentSerializers.Create.get_document_paragraph_model(knowledge_id, document) document_model_list.append(document_paragraph_dict_model.get('document')) for paragraph in document_paragraph_dict_model.get('paragraph_model_list'): paragraph_model_list.append(paragraph) for problem_paragraph_object in document_paragraph_dict_model.get('problem_paragraph_object_list'): problem_paragraph_object_list.append(problem_paragraph_object) problem_model_list, problem_paragraph_mapping_list = ( ProblemParagraphManage(problem_paragraph_object_list, knowledge_id) .to_problem_model_list()) # 插入知识库 knowledge.save() # 插入文档 QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None # 批量插入段落 QuerySet(Paragraph).bulk_create(paragraph_model_list) if len(paragraph_model_list) > 0 else None # 批量插入问题 QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None # 批量插入关联问题 QuerySet(ProblemParagraphMapping).bulk_create( problem_paragraph_mapping_list ) if len(problem_paragraph_mapping_list) > 0 else None # 自动资源给授权当前用户 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.KNOWLEDGE.value }).auth_resource(str(knowledge_id)) update_resource_mapping_by_knowledge(str(knowledge_id)) return { **KnowledgeModelSerializer(knowledge).data, 'user_id': self.data.get('user_id'), 'document_list': document_model_list, "document_count": len(document_model_list), "char_length": reduce(lambda x, y: x + y, [d.char_length for d in document_model_list], 0) }, knowledge_id def save_web(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) KnowledgeWebCreateRequest(data=instance).is_valid(raise_exception=True) folder_id = instance.get('folder_id', self.data.get('workspace_id')) knowledge_id = uuid.uuid7() knowledge = Knowledge( id=knowledge_id, name=instance.get('name'), desc=instance.get('desc'), user_id=self.data.get('user_id'), type=instance.get('type', KnowledgeType.WEB), scope=self.data.get('scope', KnowledgeScope.WORKSPACE), folder_id=folder_id, workspace_id=self.data.get('workspace_id'), embedding_model_id=instance.get('embedding_model_id'), meta={ 'source_url': instance.get('source_url'), 'selector': instance.get('selector', 'body'), 'embedding_model_id': instance.get('embedding_model_id') }, ) knowledge.save() # 自动资源给授权当前用户 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.KNOWLEDGE.value }).auth_resource(str(knowledge_id)) sync_web_knowledge.delay(str(knowledge_id), instance.get('source_url'), instance.get('selector')) update_resource_mapping_by_knowledge(str(knowledge_id)) return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []} class SyncWeb(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.CharField(required=True, label=_('knowledge id')) user_id = serializers.UUIDField(required=False, label=_('user id')) sync_type = serializers.CharField(required=True, label=_('sync type'), validators=[ validators.RegexValidator(regex=re.compile("^replace|complete$"), message=_('The synchronization type only supports:replace|complete'), code=500)]) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) first = QuerySet(Knowledge).filter(id=self.data.get("knowledge_id")).first() if first is None: raise AppApiException(300, _('id does not exist')) if first.type != KnowledgeType.WEB: raise AppApiException(500, _('Synchronization is only supported for web site types')) def sync(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) sync_type = self.data.get('sync_type') knowledge_id = self.data.get('knowledge_id') knowledge = QuerySet(Knowledge).get(id=knowledge_id) self.__getattribute__(sync_type + '_sync')(knowledge) return True @staticmethod def get_sync_handler(knowledge): def handler(child_link: ChildLink, response: Fork.Response): if response.status == 200: try: document_name = child_link.tag.text if child_link.tag is not None and len( child_link.tag.text.strip()) > 0 else child_link.url paragraphs = get_split_model('web.md').parse(response.content) maxkb_logger.info(child_link.url.strip()) first = QuerySet(Document).filter( meta__source_url=child_link.url.strip(), knowledge=knowledge ).first() if first is not None: # 如果存在,使用文档同步 DocumentSerializers.Sync(data={'document_id': first.id}).sync() else: # 插入 DocumentSerializers.Create(data={'knowledge_id': knowledge.id}).save( {'name': document_name, 'paragraphs': paragraphs, 'meta': {'source_url': child_link.url.strip(), 'selector': knowledge.meta.get('selector')}, 'type': KnowledgeType.WEB}, with_valid=True) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') return handler def replace_sync(self, knowledge): """ 替换同步 :return: """ url = knowledge.meta.get('source_url') selector = knowledge.meta.get('selector') if 'selector' in knowledge.meta else None sync_replace_web_knowledge.delay(str(knowledge.id), url, selector) def complete_sync(self, knowledge): """ 完整同步 删掉当前数据集下所有的文档,再进行同步 :return: """ # 删除关联问题 QuerySet(ProblemParagraphMapping).filter(knowledge=knowledge).delete() # 删除文档 QuerySet(Document).filter(knowledge=knowledge).delete() # 删除段落 QuerySet(Paragraph).filter(knowledge=knowledge).delete() # 删除向量 delete_embedding_by_knowledge(self.data.get('knowledge_id')) # 同步 self.replace_sync(knowledge) class HitTest(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_("id")) user_id = serializers.UUIDField(required=False, label=_('user id')) query_text = serializers.CharField(required=True, label=_('query text')) top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1, label=_("top number")) similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_('similarity')) search_mode = serializers.CharField(required=True, label=_('search mode'), validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_('The type only supports embedding|keywords|blend'), code=500) ]) def is_valid(self, *, raise_exception=True): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Knowledge).filter(id=self.data.get("knowledge_id")).exists(): raise AppApiException(300, _('id does not exist')) def hit_test(self): self.is_valid() vector = VectorStore.get_embedding_vector() exclude_document_id_list = [ str( document.id ) for document in QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id'), is_active=False) ] model = get_embedding_model_by_knowledge_id(self.data.get('knowledge_id')) # 向量库检索 hit_list = vector.hit_test( self.data.get('query_text'), [self.data.get('knowledge_id')], exclude_document_id_list, self.data.get('top_number'), self.data.get('similarity'), SearchMode(self.data.get('search_mode')), model ) hit_dict = reduce(lambda x, y: {**x, **y}, [{hit.get('paragraph_id'): hit} for hit in hit_list], {}) p_list = list_paragraph([h.get('paragraph_id') for h in hit_list]) return [ { **p, 'similarity': hit_dict.get(p.get('id')).get('similarity'), 'comprehensive_score': hit_dict.get(p.get('id')).get('comprehensive_score') } for p in p_list ] class StoreKnowledge(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True) def get_appstore_templates(self): self.is_valid(raise_exception=True) # 下载zip文件 try: res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5) res.raise_for_status() # 创建临时文件保存zip with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip: temp_zip.write(res.content) temp_zip_path = temp_zip.name try: # 解压zip文件 with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref: # 获取zip中的第一个文件(假设只有一个json文件) json_filename = zip_ref.namelist()[0] json_content = zip_ref.read(json_filename) # 将json转换为字典 tool_store = json.loads(json_content.decode('utf-8')) tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']} filter_apps = [] for tool in tool_store['apps']: if self.data.get('name', '') != '': if self.data.get('name').lower() not in tool.get('name', '').lower(): continue if not tool['downloadUrl'].endswith('.kbwf'): continue versions = tool.get('versions', []) tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else '' tool['version'] = next( (version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']), ) filter_apps.append(tool) tool_store['apps'] = filter_apps return tool_store finally: # 清理临时文件 os.unlink(temp_zip_path) except Exception as e: maxkb_logger.error(f"fetch appstore tools error: {e}") return {'apps': [], 'additionalProperties': {'tags': []}} class TransformWorkflow(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('Workspace ID')) knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID')) user_id = serializers.UUIDField(required=True, label=_('User ID')) def transform(self, instance: Dict): self.is_valid(raise_exception=True) knowledge = QuerySet(Knowledge).filter( id=self.data.get('knowledge_id'), workspace_id=self.data.get('workspace_id') ).first() if not knowledge: raise AppApiException(500, _('Knowledge not found')) if knowledge.type == KnowledgeType.WORKFLOW: raise AppApiException(500, _('Knowledge is already a workflow')) knowledge.type = KnowledgeType.WORKFLOW knowledge.save() workflow_id = uuid.uuid7() knowledge_workflow = KnowledgeWorkflow( id=workflow_id, workspace_id=knowledge.workspace_id, knowledge_id=knowledge.id, work_flow=instance.get('work_flow', {}), ) knowledge_workflow.save() return True class Tags(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_('user id')) knowledge_ids = serializers.ListField( required=True, label=_('knowledge ids'), child=serializers.UUIDField(required=True, label=_('id')) ) def list(self): self.is_valid(raise_exception=True) if self.data.get('name'): name = self.data.get('name') tags = QuerySet(Tag).filter( knowledge_id__in=self.data.get('knowledge_ids') ).filter( Q(key__icontains=name) | Q(value__icontains=name) ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value') else: # 获取所有标签,按创建时间排序保持稳定顺序 tags = QuerySet(Tag).filter( knowledge_id__in=self.data.get('knowledge_ids') ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value') # 按key分组 grouped_tags = defaultdict(list) for tag in tags: grouped_tags[tag['key']].append({ 'id': tag['id'], 'value': tag['value'], 'create_time': tag['create_time'], 'update_time': tag['update_time'] }) # 转换为期望的格式,保持key的顺序 result = [] # 按key排序以确保结果顺序一致 for key in sorted(grouped_tags.keys()): values = grouped_tags[key] # 按创建时间对values进行排序 values.sort(key=lambda x: x['create_time']) result.append({ 'key': key, 'values': values, }) return result ================================================ FILE: apps/knowledge/serializers/knowledge_folder.py ================================================ from rest_framework import serializers from knowledge.models import KnowledgeFolder class KnowledgeFolderTreeSerializer(serializers.ModelSerializer): children = serializers.SerializerMethodField() class Meta: model = KnowledgeFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children', 'create_time','update_time'] def get_children(self, obj): return KnowledgeFolderTreeSerializer(obj.get_children(), many=True).data class KnowledgeFolderFlatSerializer(serializers.ModelSerializer): class Meta: model = KnowledgeFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id'] ================================================ FILE: apps/knowledge/serializers/knowledge_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: KnowledgeVersionSerializer.py @date:2025/11/28 18:00 @desc: """ from typing import Dict from django.db.models import QuerySet from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.db.search import page_search from common.exception.app_exception import AppApiException from knowledge.models import KnowledgeWorkflowVersion, Knowledge class KnowledgeWorkflowVersionEditSerializer(serializers.Serializer): name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True, label=_("Version Name")) class KnowledgeVersionModelSerializer(serializers.ModelSerializer): class Meta: model = KnowledgeWorkflowVersion fields = ['id', 'name', 'workspace_id', 'knowledge_id', 'work_flow', 'publish_user_id', 'publish_user_name', 'create_time', 'update_time'] class KnowledgeWorkflowVersionQuerySerializer(serializers.Serializer): knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge ID")) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("summary")) class KnowledgeWorkflowVersionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_("Workspace ID")) class Query(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def get_query_set(self, query): query_set = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=query.get('knowledge_id')) if 'name' in query and query.get('name') is not None: query_set = query_set.filter(name__contains=query.get('name')) if 'workspace_id' in self.data and self.data.get('workspace_id') is not None: query_set = query_set.filter(workspace_id=self.data.get('workspace_id')) return query_set.order_by("-create_time") def list(self, query, with_valid=True): if with_valid: self.is_valid(raise_exception=True) KnowledgeWorkflowVersionQuerySerializer(data=query).is_valid(raise_exception=True) query_set = self.get_query_set(query) return [KnowledgeVersionModelSerializer(v).data for v in query_set] def page(self, query, current_page, page_size, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return page_search(current_page, page_size, self.get_query_set(query), post_records_handler=lambda v: KnowledgeVersionModelSerializer(v).data) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge ID")) knowledge_version_id = serializers.UUIDField(required=True, label=_("Knowledge version ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_version = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=self.data.get('knowledge_id'), id=self.data.get( 'knowledge_version_id')).first() if knowledge_version is not None: return KnowledgeVersionModelSerializer(knowledge_version).data else: raise AppApiException(500, _('Workflow version does not exist')) def edit(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) KnowledgeWorkflowVersionEditSerializer(data=instance).is_valid(raise_exception=True) knowledge_version = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=self.data.get('knowledge_id'), id=self.data.get( 'knowledge_version_id')).first() if knowledge_version is not None: name = instance.get('name', None) if name is not None and len(name) > 0: knowledge_version.name = name knowledge_version.save() return KnowledgeVersionModelSerializer(knowledge_version).data else: raise AppApiException(500, _('Workflow version does not exist')) ================================================ FILE: apps/knowledge/serializers/knowledge_workflow.py ================================================ # coding=utf-8 import asyncio import json import pickle from functools import reduce from typing import Dict, List import requests import uuid_utils.compat as uuid from django.core.cache import cache from django.db import transaction from django.db.models import QuerySet from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework import serializers, status from application.flow.common import Workflow, WorkflowMode from application.flow.i_step_node import KnowledgeWorkflowPostHandler from application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage from application.flow.step_node import get_node from application.flow.tools import save_workflow_mapping from application.serializers.application import get_mcp_tools from common.constants.cache_version import Cache_Version from common.db.search import page_search from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField from common.result import result from common.utils.common import bytes_to_uploaded_file from common.utils.common import restricted_loads, generate_uuid from common.utils.logger import maxkb_logger from common.utils.rsa_util import rsa_long_decrypt from common.utils.tool_code import ToolExecutor from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion from knowledge.models.knowledge_action import KnowledgeAction, State from knowledge.serializers.common import update_resource_mapping_by_knowledge from knowledge.serializers.knowledge import KnowledgeModelSerializer from system_manage.models import AuthTargetType from system_manage.models.resource_mapping import ResourceType from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from tools.models import Tool, ToolScope from tools.serializers.tool import ToolExportModelSerializer from users.models import User tool_executor = ToolExecutor() def hand_node(node, update_tool_map): if node.get('type') == 'tool-lib-node': tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '') node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id) if node.get('type') == 'search-knowledge-node': node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = [] if node.get('type') == 'ai-chat-node': node_data = node.get('properties', {}).get('node_data', {}) mcp_tool_ids = node_data.get('mcp_tool_ids') or [] node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in mcp_tool_ids] tool_ids = node_data.get('tool_ids') or [] node_data['tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in tool_ids] if node.get('type') == 'mcp-node': mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '') node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id, mcp_tool_id) class KnowledgeWorkflowModelSerializer(serializers.ModelSerializer): class Meta: model = KnowledgeWorkflow fields = '__all__' class KnowledgeWorkflowActionRequestSerializer(serializers.Serializer): data_source = serializers.DictField(required=True, label=_('datasource data')) knowledge_base = serializers.DictField(required=True, label=_('knowledge base data')) class KnowledgeWorkflowImportRequest(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer): user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True) state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True) class KBWFInstance: def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]): self.knowledge_workflow = knowledge_workflow self.function_lib_list = function_lib_list self.version = version self.tool_list = tool_list def get_tool_list(self): return [*(self.tool_list or []), *(self.function_lib_list or [])] class KnowledgeWorkflowActionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def get_query_set(self, instance: Dict): query_set = QuerySet(KnowledgeAction).filter(knowledge_id=self.data.get('knowledge_id')).values('id', 'knowledge_id', "state", 'meta', 'run_time', "create_time") if instance.get("user_name"): query_set = query_set.filter(meta__user_name__icontains=instance.get('user_name')) if instance.get('state'): query_set = query_set.filter(state=instance.get('state')) return query_set.order_by('-create_time') def list(self, instance: Dict, is_valid=True): if is_valid: self.is_valid(raise_exception=True) KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True) return [{'id': a.get("id"), 'knowledge_id': a.get("knowledge_id"), 'state': a.get("state"), 'meta': a.get("meta"), 'run_time': a.get("run_time"), 'create_time': a.get("create_time")} for a in self.get_query_set(instance)] def page(self, current_page, page_size, instance: Dict, is_valid=True): if is_valid: self.is_valid(raise_exception=True) KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True) return page_search(current_page, page_size, self.get_query_set(instance), lambda a: {'id': a.get("id"), 'knowledge_id': a.get("knowledge_id"), 'state': a.get("state"), 'meta': a.get("meta"), 'run_time': a.get("run_time"), 'create_time': a.get("create_time")}) def action(self, instance: Dict, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get("knowledge_id")).first() knowledge_action_id = uuid.uuid7() meta = {'user_id': str(user.id), 'user_name': user.username} KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get("knowledge_id"), state=State.STARTED, meta=meta).save() knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first() instance['knowledge_base'] = {**(instance.get('knowledge_base') or {}), 'knowledge': {'id': str(knowledge.id), 'name': knowledge.name, 'desc': knowledge.desc, 'workspace_id': knowledge.workspace_id}} work_flow_manage = KnowledgeWorkflowManage( Workflow.new_instance(knowledge_workflow.work_flow, WorkflowMode.KNOWLEDGE), {'knowledge_id': self.data.get("knowledge_id"), 'knowledge_action_id': knowledge_action_id, 'stream': True, 'workspace_id': self.data.get("workspace_id"), **instance}, KnowledgeWorkflowPostHandler(None, knowledge_action_id), is_the_task_interrupted=lambda: cache.get( Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) or False) work_flow_manage.run() return {'id': knowledge_action_id, 'knowledge_id': self.data.get("knowledge_id"), 'state': State.STARTED, 'details': {}, 'meta': meta} def upload_document(self, instance: Dict, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get("knowledge_id")).first() if not knowledge_workflow.is_publish: raise AppApiException(500, _("The knowledge base workflow has not been published")) knowledge_workflow_version = QuerySet(KnowledgeWorkflowVersion).filter( knowledge_id=self.data.get("knowledge_id")).order_by( '-create_time')[0:1].first() knowledge_action_id = uuid.uuid7() meta = {'user_id': str(user.id), 'user_name': user.username} KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get("knowledge_id"), state=State.STARTED, meta=meta).save() knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first() instance['knowledge_base'] = {**(instance.get('knowledge_base') or {}), 'knowledge': {'id': str(knowledge.id), 'name': knowledge.name, 'desc': knowledge.desc, 'workspace_id': knowledge.workspace_id}} work_flow_manage = KnowledgeWorkflowManage( Workflow.new_instance(knowledge_workflow_version.work_flow, WorkflowMode.KNOWLEDGE), {'knowledge_id': self.data.get("knowledge_id"), 'knowledge_action_id': knowledge_action_id, 'stream': True, 'workspace_id': self.data.get("workspace_id"), **instance}, KnowledgeWorkflowPostHandler(None, knowledge_action_id), is_the_task_interrupted=lambda: cache.get( Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) or False ) work_flow_manage.run() return {'id': knowledge_action_id, 'knowledge_id': self.data.get("knowledge_id"), 'state': State.STARTED, 'details': {}, 'meta': meta} class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) id = serializers.UUIDField(required=True, label=_('knowledge action id')) def one(self, is_valid=True): if is_valid: self.is_valid(raise_exception=True) knowledge_action_id = self.data.get("id") knowledge_action = QuerySet(KnowledgeAction).filter(id=knowledge_action_id).first() return {'id': knowledge_action_id, 'knowledge_id': knowledge_action.knowledge_id, 'state': knowledge_action.state, 'details': knowledge_action.details, 'meta': knowledge_action.meta} def cancel(self, is_valid=True): if is_valid: self.is_valid(raise_exception=True) knowledge_action_id = self.data.get("id") cache.set(Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), True, version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) QuerySet(KnowledgeAction).filter(id=knowledge_action_id, state__in=[State.STARTED, State.PENDING]).update( state=State.REVOKE) return True class KnowledgeWorkflowSerializer(serializers.Serializer): class Datasource(serializers.Serializer): type = serializers.CharField(required=True, label=_('type')) id = serializers.CharField(required=True, label=_('type')) params = serializers.DictField(required=True, label="") function_name = serializers.CharField(required=True, label=_('function_name')) def action(self): self.is_valid(raise_exception=True) if self.data.get('type') == 'local': node = get_node(self.data.get('id'), WorkflowMode.KNOWLEDGE) return node.__getattribute__(node, self.data.get("function_name"))(**self.data.get("params")) elif self.data.get('type') == 'tool': tool = QuerySet(Tool).filter(id=self.data.get("id")).first() init_params = json.loads(rsa_long_decrypt(tool.init_params)) return tool_executor.exec_code(tool.code, {**init_params, **self.data.get('params')}, self.data.get('function_name')) class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) scope = serializers.ChoiceField( required=False, label=_('scope'), default=KnowledgeScope.WORKSPACE, choices=KnowledgeScope.choices ) @transaction.atomic def save_workflow(self, instance: Dict): self.is_valid(raise_exception=True) folder_id = instance.get('folder_id', self.data.get('workspace_id')) knowledge_id = uuid.uuid7() knowledge = Knowledge( id=knowledge_id, name=instance.get('name'), desc=instance.get('desc'), user_id=self.data.get('user_id'), type=instance.get('type', KnowledgeType.WORKFLOW), scope=self.data.get('scope', KnowledgeScope.WORKSPACE), folder_id=folder_id, workspace_id=self.data.get('workspace_id'), embedding_model_id=instance.get('embedding_model_id'), meta={}, ) knowledge.save() # 自动资源给授权当前用户 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.KNOWLEDGE.value }).auth_resource(str(knowledge_id)) knowledge_workflow = KnowledgeWorkflow( id=uuid.uuid7(), knowledge_id=knowledge_id, workspace_id=self.data.get('workspace_id'), work_flow=instance.get('work_flow', {}), ) knowledge_workflow.save() save_workflow_mapping(instance.get('work_flow', {}), ResourceType.KNOWLEDGE, str(knowledge_id)) # 处理 work_flow_template if instance.get('work_flow_template') is not None: template_instance = instance.get('work_flow_template') download_url = template_instance.get('downloadUrl') # 查找匹配的版本名称 res = requests.get(download_url, timeout=5) KnowledgeWorkflowSerializer.Import(data={ 'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id'), 'knowledge_id': str(knowledge_id), }).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=True) try: requests.get(template_instance.get('downloadCallbackUrl'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []} class Import(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) @transaction.atomic def import_(self, instance: dict, is_import_tool, with_valid=True): if with_valid: self.is_valid() KnowledgeWorkflowImportRequest(data=instance).is_valid(raise_exception=True) user_id = self.data.get('user_id') workspace_id = self.data.get('workspace_id') knowledge_id = self.data.get('knowledge_id') kbwf_instance_bytes = instance.get('file').read() try: kbwf_instance = restricted_loads(kbwf_instance_bytes) except Exception as e: raise AppApiException(1001, _("Unsupported file format")) knowledge_workflow = kbwf_instance.knowledge_workflow tool_list = kbwf_instance.get_tool_list() update_tool_map = {} if len(tool_list) > 0: tool_id_list = reduce(lambda x, y: [*x, *y], [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))] for tool in tool_list], []) # 存在的工具列表 exits_tool_id_list = [str(tool.id) for tool in QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)] # 需要更新的工具集合 update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id'))} tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id')) and not exits_tool_id_list.__contains__( generate_uuid((tool.get('id') + workspace_id or '')))] work_flow = self.to_knowledge_workflow( knowledge_workflow, update_tool_map, ) tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list] KnowledgeWorkflow.objects.filter(workspace_id=workspace_id, knowledge_id=knowledge_id).update_or_create( knowledge_id=knowledge_id, workspace_id=workspace_id, defaults={'work_flow': work_flow} ) if is_import_tool: if len(tool_model_list) > 0: QuerySet(Tool).bulk_create(tool_model_list) UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource_batch([t.id for t in tool_model_list]) return True update_resource_mapping_by_knowledge(knowledge_id) @staticmethod def to_knowledge_workflow(knowledge_workflow, update_tool_map): work_flow = knowledge_workflow.get("work_flow") for node in work_flow.get('nodes', []): hand_node(node, update_tool_map) if node.get('type') == 'loop_node': for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): hand_node(n, update_tool_map) return work_flow @staticmethod def to_tool(tool, workspace_id, user_id): return Tool(id=tool.get('id'), user_id=user_id, name=tool.get('name'), code=tool.get('code'), template_id=tool.get('template_id'), input_field_list=tool.get('input_field_list'), init_field_list=tool.get('init_field_list'), is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM', scope=ToolScope.SHARED if workspace_id == 'None' else ToolScope.WORKSPACE, folder_id='default' if workspace_id == 'None' else workspace_id, workspace_id=workspace_id) class Export(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def export(self, with_valid=True): try: if with_valid: self.is_valid() knowledge_id = self.data.get('knowledge_id') knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_id).first() knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() from application.flow.tools import get_tool_id_list tool_id_list = get_tool_id_list(knowledge_workflow.work_flow) tool_list = [] if len(tool_id_list) > 0: tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) knowledge_workflow_dict = KnowledgeWorkflowModelSerializer(knowledge_workflow).data kbwf_instance = KBWFInstance( knowledge_workflow_dict, [], 'v2', [ToolExportModelSerializer(tool).data for tool in tool_list] ) knowledge_workflow_pickle = pickle.dumps(kbwf_instance) response = HttpResponse(content_type='text/plain', content=knowledge_workflow_pickle) response['Content-Disposition'] = f'attachment; filename="{knowledge.name}.kbwf"' return response except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) class Operate(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def publish(self, with_valid=True): if with_valid: self.is_valid() user_id = self.data.get('user_id') workspace_id = self.data.get("workspace_id") user = QuerySet(User).filter(id=user_id).first() knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get("knowledge_id"), workspace_id=workspace_id).first() work_flow_version = KnowledgeWorkflowVersion(work_flow=knowledge_workflow.work_flow, knowledge_id=self.data.get("knowledge_id"), name=timezone.localtime(timezone.now()).strftime( '%Y-%m-%d %H:%M:%S'), publish_user_id=user_id, publish_user_name=user.username, workspace_id=workspace_id) work_flow_version.save() QuerySet(KnowledgeWorkflow).filter( knowledge_id=self.data.get("knowledge_id") ).update(is_publish=True, publish_time=timezone.now()) return True def edit(self, instance: Dict): self.is_valid(raise_exception=True) if instance.get("work_flow"): QuerySet(KnowledgeWorkflow).update_or_create(knowledge_id=self.data.get("knowledge_id"), create_defaults={'id': uuid.uuid7(), 'knowledge_id': self.data.get( "knowledge_id"), "workspace_id": self.data.get( 'workspace_id'), 'work_flow': instance.get('work_flow', {}), }, defaults={ 'work_flow': instance.get('work_flow') }) update_resource_mapping_by_knowledge(self.data.get("knowledge_id")) return self.one() if instance.get("work_flow_template"): template_instance = instance.get('work_flow_template') download_url = template_instance.get('downloadUrl') # 查找匹配的版本名称 res = requests.get(download_url, timeout=5) KnowledgeWorkflowSerializer.Import(data={ 'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id'), 'knowledge_id': str(self.data.get('knowledge_id')), }).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=False) try: requests.get(template_instance.get('downloadCallbackUrl'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return self.one() def one(self): self.is_valid(raise_exception=True) workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first() return {**KnowledgeWorkflowModelSerializer(workflow).data} class McpServersSerializer(serializers.Serializer): mcp_servers = serializers.JSONField(required=True) class KnowledgeWorkflowMcpSerializer(serializers.Serializer): knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def get_mcp_servers(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) McpServersSerializer(data=instance).is_valid(raise_exception=True) servers = json.loads(instance.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')) tools = [] for server in servers: tools += [ { 'server': server, 'name': tool.name, 'description': tool.description, 'args_schema': tool.args_schema, } for tool in asyncio.run(get_mcp_tools({server: servers[server]}))] return tools ================================================ FILE: apps/knowledge/serializers/paragraph.py ================================================ # coding=utf-8 from typing import Dict import uuid_utils.compat as uuid from celery_once import AlreadyQueued from django.db import transaction from django.db.models import QuerySet, Count, F from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.db.search import page_search from common.event.listener_manage import ListenerManagement from common.exception.app_exception import AppApiException from common.utils.common import post from knowledge.models import Paragraph, Problem, Document, ProblemParagraphMapping, SourceType, TaskType, State, \ Knowledge from knowledge.serializers.common import ProblemParagraphObject, ProblemParagraphManage, \ get_embedding_model_id_by_knowledge_id, update_document_char_length, BatchSerializer from knowledge.serializers.problem import ProblemInstanceSerializer, ProblemSerializer, ProblemSerializers from knowledge.task.embedding import embedding_by_paragraph, enable_embedding_by_paragraph, \ disable_embedding_by_paragraph, \ delete_embedding_by_paragraph, embedding_by_problem as embedding_by_problem_task, delete_embedding_by_paragraph_ids, \ embedding_by_problem, delete_embedding_by_source, update_embedding_document_id from knowledge.task.generate import generate_related_by_paragraph_id_list class ParagraphSerializer(serializers.ModelSerializer): class Meta: model = Paragraph fields = ['id', 'content', 'is_active', 'document_id', 'title', 'create_time', 'update_time', 'position'] class ParagraphInstanceSerializer(serializers.Serializer): """ 段落实例对象 """ content = serializers.CharField(required=True, label=_('content'), max_length=102400, min_length=1, allow_null=True, allow_blank=True) title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True, allow_blank=True) problem_list = ProblemInstanceSerializer(required=False, many=True) is_active = serializers.BooleanField(required=False, label=_('Is active')) class EditParagraphSerializers(serializers.Serializer): title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True, allow_blank=True) content = serializers.CharField(required=False, max_length=102400, allow_null=True, allow_blank=True, label=_('section title')) problem_list = ProblemInstanceSerializer(required=False, many=True) class ParagraphBatchGenerateRelatedSerializer(serializers.Serializer): paragraph_id_list = serializers.ListField(required=True, label=_('paragraph id list'), child=serializers.UUIDField(required=True, label=_('paragraph id'))) model_id = serializers.UUIDField(required=True, label=_('model id')) prompt = serializers.CharField(required=True, label=_('prompt'), max_length=102400, allow_null=True, allow_blank=True) document_id = serializers.UUIDField(required=True, label=_('document id')) class ParagraphSerializers(serializers.Serializer): title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True, allow_blank=True) content = serializers.CharField(required=True, max_length=102400, label=_('section title')) class Problem(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Paragraph).filter(id=self.data.get('paragraph_id')).exists(): raise AppApiException(500, _('Paragraph id does not exist')) def list(self, with_valid=False): """ 获取问题列表 :param with_valid: 是否校验 :return: 问题列表 """ if with_valid: self.is_valid(raise_exception=True) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( knowledge_id=self.data.get("knowledge_id"), paragraph_id=self.data.get( 'paragraph_id')) return [ProblemSerializer(row).data for row in QuerySet(Problem).filter(id__in=[row.problem_id for row in problem_paragraph_mapping])] @transaction.atomic def save(self, instance: Dict, with_valid=True, with_embedding=True, embedding_by_problem=None): if with_valid: self.is_valid() ProblemInstanceSerializer(data=instance).is_valid(raise_exception=True) problem = QuerySet(Problem).filter(knowledge_id=self.data.get('knowledge_id'), content=instance.get('content')).first() if problem is None: problem = Problem(id=uuid.uuid7(), knowledge_id=self.data.get('knowledge_id'), content=instance.get('content')) problem.save() if QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get('knowledge_id'), problem_id=problem.id, paragraph_id=self.data.get('paragraph_id')).exists(): raise AppApiException(500, _('Already associated, please do not associate again')) problem_paragraph_mapping = ProblemParagraphMapping( id=uuid.uuid7(), problem_id=problem.id, document_id=self.data.get('document_id'), paragraph_id=self.data.get('paragraph_id'), knowledge_id=self.data.get('knowledge_id') ) problem_paragraph_mapping.save() model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id')) if with_embedding: embedding_by_problem_task({ 'text': problem.content, 'is_active': True, 'source_type': SourceType.PROBLEM, 'source_id': problem_paragraph_mapping.id, 'document_id': self.data.get('document_id'), 'paragraph_id': self.data.get('paragraph_id'), 'knowledge_id': self.data.get('knowledge_id'), }, model_id) return ProblemSerializers.Operate( data={ 'workspace_id': self.data.get('workspace_id'), 'knowledge_id': self.data.get('knowledge_id'), 'problem_id': problem.id } ).one(with_valid=True) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) # 段落id paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) # 知识库id knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) # 文档id document_id = serializers.UUIDField(required=True, label=_('document id')) def is_valid(self, *, raise_exception=True): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Paragraph).filter(id=self.data.get('paragraph_id')).exists(): raise AppApiException(500, _('Paragraph id does not exist')) @staticmethod def post_embedding(paragraph, instance, knowledge_id): if 'is_active' in instance and instance.get('is_active') is not None: (enable_embedding_by_paragraph if instance.get( 'is_active') else disable_embedding_by_paragraph)(paragraph.get('id')) else: model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) embedding_by_paragraph(paragraph.get('id'), model_id) return paragraph @post(post_embedding) @transaction.atomic def edit(self, instance: Dict): self.is_valid() EditParagraphSerializers(data=instance).is_valid(raise_exception=True) _paragraph = QuerySet(Paragraph).get(id=self.data.get("paragraph_id")) update_keys = ['title', 'content', 'is_active'] for update_key in update_keys: if update_key in instance and instance.get(update_key) is not None: _paragraph.__setattr__(update_key, instance.get(update_key)) if 'problem_list' in instance: update_problem_list = list( filter(lambda row: 'id' in row and row.get('id') is not None, instance.get('problem_list'))) create_problem_list = list(filter(lambda row: row.get('id') is None, instance.get('problem_list'))) # 问题集合 problem_list = QuerySet(Problem).filter(paragraph_id=self.data.get("paragraph_id")) # 校验前端 携带过来的id for update_problem in update_problem_list: if not set([str(row.id) for row in problem_list]).__contains__(update_problem.get('id')): raise AppApiException(500, _('Problem id does not exist')) # 对比需要删除的问题 delete_problem_list = list(filter( lambda row: not [str(update_row.get('id')) for update_row in update_problem_list].__contains__( str(row.id)), problem_list)) if len(update_problem_list) > 0 else [] # 删除问题 QuerySet(Problem).filter(id__in=[row.id for row in delete_problem_list]).delete() if len( delete_problem_list) > 0 else None # 插入新的问题 QuerySet(Problem).bulk_create([ Problem( id=uuid.uuid7(), content=p.get('content'), paragraph_id=self.data.get('paragraph_id'), knowledge_id=self.data.get('knowledge_id'), document_id=self.data.get('document_id') ) for p in create_problem_list ]) if len(create_problem_list) else None # 修改问题集合 QuerySet(Problem).bulk_update([ Problem( id=row.get('id'), content=row.get('content') ) for row in update_problem_list], ['content'] ) if len(update_problem_list) > 0 else None _paragraph.save() update_document_char_length(self.data.get('document_id')) return self.one(), instance, self.data.get('knowledge_id') def get_problem_list(self): ProblemParagraphMapping(ProblemParagraphMapping) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( paragraph_id=self.data.get("paragraph_id")) if len(problem_paragraph_mapping) > 0: return [ProblemSerializer(problem).data for problem in QuerySet(Problem).filter(id__in=[ppm.problem_id for ppm in problem_paragraph_mapping])] return [] def one(self, with_valid=False): if with_valid: self.is_valid(raise_exception=True) return {**ParagraphSerializer(QuerySet(model=Paragraph).get(id=self.data.get('paragraph_id'))).data, 'problem_list': self.get_problem_list()} def delete(self, with_valid=False): if with_valid: self.is_valid(raise_exception=True) paragraph_id = self.data.get('paragraph_id') Paragraph.objects.filter(id=paragraph_id).delete() delete_problems_and_mappings([paragraph_id]) update_document_char_length(self.data.get('document_id')) delete_embedding_by_paragraph(paragraph_id) class Create(serializers.Serializer): workspace_id = serializers.CharField(required=True, label='Workspace ID') knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if not QuerySet(Document).filter(id=self.data.get('document_id'), knowledge_id=self.data.get('knowledge_id')).exists(): raise AppApiException(500, _('The document id is incorrect')) @transaction.atomic def save(self, instance: Dict, with_valid=True, with_embedding=True): if with_valid: ParagraphSerializers(data=instance).is_valid(raise_exception=True) self.is_valid() knowledge_id = self.data.get("knowledge_id") document_id = self.data.get('document_id') # 先将同一文档中的所有段落位置向下移动一位 Paragraph.objects.filter(document_id=document_id).update(position=F('position') + 1) paragraph_problem_model = self.get_paragraph_problem_model(knowledge_id, document_id, instance) paragraph = paragraph_problem_model.get('paragraph') problem_paragraph_object_list = paragraph_problem_model.get('problem_paragraph_object_list') problem_model_list, problem_paragraph_mapping_list = ( ProblemParagraphManage(problem_paragraph_object_list, knowledge_id) .to_problem_model_list()) # 新加的在最上面 paragraph.position = 1 paragraph.save() # 调整位置 if 'position' in instance: if type(instance['position']) is not int: instance['position'] = 1 else: instance['position'] = 1 ParagraphSerializers.AdjustPosition(data={ 'paragraph_id': str(paragraph.id), 'knowledge_id': knowledge_id, 'document_id': document_id, 'workspace_id': self.data.get('workspace_id') }).adjust_position(instance.get('position')) # 插入問題 QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None # 插入问题关联关系 QuerySet(ProblemParagraphMapping).bulk_create( problem_paragraph_mapping_list ) if len(problem_paragraph_mapping_list) > 0 else None # 修改长度 update_document_char_length(document_id) if with_embedding: model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) embedding_by_paragraph(str(paragraph.id), model_id) ListenerManagement.update_status( QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.SUCCESS ) ListenerManagement.get_aggregation_document_status(document_id)() return ParagraphSerializers.Operate( data={ 'paragraph_id': str(paragraph.id), 'knowledge_id': knowledge_id, 'document_id': document_id, 'workspace_id': self.data.get('workspace_id') } ).one(with_valid=True) @staticmethod def get_paragraph_problem_model(knowledge_id: str, document_id: str, instance: Dict): paragraph = Paragraph( id=uuid.uuid7(), document_id=document_id, content=instance.get("content"), knowledge_id=knowledge_id, title=instance.get("title") if 'title' in instance else '' ) problem_paragraph_object_list = [ProblemParagraphObject( knowledge_id, document_id, str(paragraph.id), problem.get('content') ) for problem in (instance.get('problem_list') if 'problem_list' in instance else [])] return { 'paragraph': paragraph, 'problem_paragraph_object_list': problem_paragraph_object_list } @staticmethod def or_get(exists_problem_list, content, knowledge_id): exists = [row for row in exists_problem_list if row.content == content] if len(exists) > 0: return exists[0] else: return Problem(id=uuid.uuid7(), content=content, knowledge_id=knowledge_id) class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) title = serializers.CharField(required=False, label=_('section title')) content = serializers.CharField(required=False) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def get_query_set(self): self.is_valid() query_set = QuerySet(model=Paragraph) query_set = query_set.filter( **{'knowledge_id': self.data.get('knowledge_id'), 'document_id': self.data.get("document_id")}) if 'title' in self.data: query_set = query_set.filter( **{'title__icontains': self.data.get('title')}) if 'content' in self.data: query_set = query_set.filter(**{'content__icontains': self.data.get('content')}) query_set = query_set.order_by('position', 'create_time') return query_set def list(self): return list(map(lambda row: ParagraphSerializer(row).data, self.get_query_set())) def page(self, current_page, page_size): query_set = self.get_query_set() return page_search(current_page, page_size, query_set, lambda row: ParagraphSerializer(row).data) class Association(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) problem_id = serializers.UUIDField(required=True, label=_('problem id')) document_id = serializers.UUIDField(required=True, label=_('document id')) paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) def is_valid(self, *, raise_exception=True): super().is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') paragraph_id = self.data.get('paragraph_id') problem_id = self.data.get("problem_id") workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) if not QuerySet(Paragraph).filter(knowledge_id=knowledge_id, id=paragraph_id).exists(): raise AppApiException(500, _('Paragraph does not exist')) if not QuerySet(Problem).filter(knowledge_id=knowledge_id, id=problem_id).exists(): raise AppApiException(500, _('Problem does not exist')) def association(self, with_valid=True, with_embedding=True): if with_valid: self.is_valid(raise_exception=True) # 已关联则直接返回 if QuerySet(ProblemParagraphMapping).filter( knowledge_id=self.data.get('knowledge_id'), document_id=self.data.get('document_id'), paragraph_id=self.data.get('paragraph_id'), problem_id=self.data.get('problem_id') ).exists(): return True problem = QuerySet(Problem).filter(id=self.data.get("problem_id")).first() problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid7(), document_id=self.data.get('document_id'), paragraph_id=self.data.get('paragraph_id'), knowledge_id=self.data.get('knowledge_id'), problem_id=problem.id) problem_paragraph_mapping.save() if with_embedding: model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id')) embedding_by_problem({ 'text': problem.content, 'is_active': True, 'source_type': SourceType.PROBLEM, 'source_id': problem_paragraph_mapping.id, 'document_id': self.data.get('document_id'), 'paragraph_id': self.data.get('paragraph_id'), 'knowledge_id': self.data.get('knowledge_id'), }, model_id) def un_association(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( paragraph_id=self.data.get('paragraph_id'), knowledge_id=self.data.get('knowledge_id'), problem_id=self.data.get( 'problem_id')).first() problem_paragraph_mapping_id = problem_paragraph_mapping.id problem_paragraph_mapping.delete() delete_embedding_by_source(problem_paragraph_mapping_id) return True class Batch(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def batch_delete(self, instance: Dict, with_valid=True): if with_valid: BatchSerializer(data=instance).is_valid(model=Paragraph, raise_exception=True) self.is_valid(raise_exception=True) paragraph_id_list = instance.get("id_list") QuerySet(Paragraph).filter(id__in=paragraph_id_list).delete() delete_problems_and_mappings(paragraph_id_list) update_document_char_length(self.data.get('document_id')) # 删除向量库 delete_embedding_by_paragraph_ids(paragraph_id_list) return True def batch_generate_related(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) paragraph_id_list = instance.get("paragraph_id_list") model_id = instance.get("model_id") prompt = instance.get("prompt") model_params_setting = instance.get("model_params_setting") document_id = self.data.get('document_id') ListenerManagement.update_status( QuerySet(Document).filter(id=document_id), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.update_status( QuerySet(Paragraph).filter(id__in=paragraph_id_list), TaskType.GENERATE_PROBLEM, State.PENDING ) ListenerManagement.get_aggregation_document_status(document_id)() try: generate_related_by_paragraph_id_list.delay(document_id, paragraph_id_list, model_id, model_params_setting, prompt) except AlreadyQueued as e: raise AppApiException(500, _('The task is being executed, please do not send it again.')) class Migrate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) target_knowledge_id = serializers.UUIDField(required=True, label=_('target knowledge id')) target_document_id = serializers.UUIDField(required=True, label=_('target document id')) paragraph_id_list = serializers.ListField(required=True, label=_('paragraph id list'), child=serializers.UUIDField(required=True, label=_('paragraph id'))) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) document_list = QuerySet(Document).filter( id__in=[self.data.get('document_id'), self.data.get('target_document_id')]) document_id = self.data.get('document_id') target_document_id = self.data.get('target_document_id') if document_id == target_document_id: raise AppApiException(5000, _('The document to be migrated is consistent with the target document')) if len([document for document in document_list if str(document.id) == self.data.get('document_id')]) < 1: raise AppApiException(5000, _('The document id does not exist [{document_id}]').format( document_id=self.data.get('document_id'))) if len([document for document in document_list if str(document.id) == self.data.get('target_document_id')]) < 1: raise AppApiException(5000, _('The target document id does not exist [{document_id}]').format( document_id=self.data.get('target_document_id'))) @transaction.atomic def migrate(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') target_knowledge_id = self.data.get('target_knowledge_id') document_id = self.data.get('document_id') target_document_id = self.data.get('target_document_id') paragraph_id_list = self.data.get('paragraph_id_list') paragraph_list = QuerySet(Paragraph).filter(knowledge_id=knowledge_id, document_id=document_id, id__in=paragraph_id_list) problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(paragraph__in=paragraph_list) # 同数据集迁移 if target_knowledge_id == knowledge_id: if len(problem_paragraph_mapping_list): problem_paragraph_mapping_list = [ self.update_problem_paragraph_mapping(target_document_id, problem_paragraph_mapping) for problem_paragraph_mapping in problem_paragraph_mapping_list] # 修改mapping QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list, ['document_id']) update_embedding_document_id([paragraph.id for paragraph in paragraph_list], target_document_id, target_knowledge_id, None) # 修改段落信息 paragraph_list.update(document_id=target_document_id) # 将当前文档中所有段落的位置向下移动,为新段落腾出空间 Paragraph.objects.filter(document_id=target_document_id).exclude( id__in=paragraph_id_list ).update(position=F('position') + len(paragraph_id_list)) # 重新查询迁移的段落 paragraph_list = Paragraph.objects.filter( id__in=paragraph_id_list, document_id=target_document_id ) # 将迁移的段落位置设置为从1开始的序号 for i, paragraph in enumerate(paragraph_list): paragraph.position = i + 1 paragraph.save() # 不同数据集迁移 else: problem_list = QuerySet(Problem).filter( id__in=[problem_paragraph_mapping.problem_id for problem_paragraph_mapping in problem_paragraph_mapping_list]) # 目标数据集问题 target_problem_list = list( QuerySet(Problem).filter(content__in=[problem.content for problem in problem_list], knowledge_id=target_knowledge_id)) target_handle_problem_list = [ self.get_target_knowledge_problem(target_knowledge_id, target_document_id, problem_paragraph_mapping, problem_list, target_problem_list) for problem_paragraph_mapping in problem_paragraph_mapping_list] create_problem_list = [problem for problem, is_create in target_handle_problem_list if is_create is not None and is_create] # 插入问题 QuerySet(Problem).bulk_create(create_problem_list) # 修改mapping QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list, ['problem_id', 'knowledge_id', 'document_id']) target_knowledge = QuerySet(Knowledge).filter(id=target_knowledge_id).first() knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() embedding_model_id = None if target_knowledge.embedding_model_id != knowledge.embedding_model_id: embedding_model_id = str(target_knowledge.embedding_model_id) pid_list = [paragraph.id for paragraph in paragraph_list] # 修改段落信息 paragraph_list.update(knowledge_id=target_knowledge_id, document_id=target_document_id) # 将当前文档中所有段落的位置向下移动,为新段落腾出空间 Paragraph.objects.filter(document_id=target_document_id).exclude( id__in=pid_list ).update(position=F('position') + len(pid_list)) # 重新查询迁移的段落 paragraph_list = Paragraph.objects.filter( id__in=pid_list, document_id=target_document_id ) # 将迁移的段落位置设置为从1开始的序号 for i, paragraph in enumerate(paragraph_list): paragraph.position = i + 1 paragraph.save() # 修改向量段落信息 update_embedding_document_id(pid_list, target_document_id, target_knowledge_id, embedding_model_id) update_document_char_length(document_id) update_document_char_length(target_document_id) @staticmethod def update_problem_paragraph_mapping(target_document_id: str, problem_paragraph_mapping): problem_paragraph_mapping.document_id = target_document_id return problem_paragraph_mapping @staticmethod def get_target_knowledge_problem(target_knowledge_id: str, target_document_id: str, problem_paragraph_mapping, source_problem_list, target_problem_list): source_problem_list = [source_problem for source_problem in source_problem_list if source_problem.id == problem_paragraph_mapping.problem_id] problem_paragraph_mapping.knowledge_id = target_knowledge_id problem_paragraph_mapping.document_id = target_document_id if len(source_problem_list) > 0: problem_content = source_problem_list[-1].content problem_list = [problem for problem in target_problem_list if problem.content == problem_content] if len(problem_list) > 0: problem = problem_list[-1] problem_paragraph_mapping.problem_id = problem.id return problem, False else: problem = Problem(id=uuid.uuid7(), knowledge_id=target_knowledge_id, content=problem_content) target_problem_list.append(problem) problem_paragraph_mapping.problem_id = problem.id return problem, True return None class AdjustPosition(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) document_id = serializers.UUIDField(required=True, label=_('document id')) paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def adjust_position(self, new_position): """ 调整段落顺序 :param new_position: 新的顺序值 """ self.is_valid(raise_exception=True) try: new_position = int(new_position) except (TypeError, ValueError): raise serializers.ValidationError(_('new_position must be an integer')) # 获取当前段落 paragraph = Paragraph.objects.get(id=self.data.get('paragraph_id')) old_position = paragraph.position if old_position < new_position: # 如果新顺序在当前顺序之后,更新受影响段落的顺序 Paragraph.objects.filter( position__gt=old_position, position__lte=new_position ).update(position=F('position') - 1) elif old_position > new_position: # 如果新顺序在当前顺序之前,更新受影响段落的顺序 Paragraph.objects.filter( position__lt=old_position, position__gte=new_position ).update(position=F('position') + 1) # 更新当前段落的顺序 paragraph.position = new_position paragraph.save() def delete_problems_and_mappings(paragraph_ids): problem_paragraph_mappings = ProblemParagraphMapping.objects.filter(paragraph_id__in=paragraph_ids) problem_ids = set(problem_paragraph_mappings.values_list('problem_id', flat=True)) if problem_ids: problem_paragraph_mappings.delete() remaining_problem_counts = ProblemParagraphMapping.objects.filter(problem_id__in=problem_ids).values( 'problem_id').annotate(count=Count('problem_id')) remaining_problem_ids = {pc['problem_id'] for pc in remaining_problem_counts} problem_ids_to_delete = problem_ids - remaining_problem_ids Problem.objects.filter(id__in=problem_ids_to_delete).delete() else: problem_paragraph_mappings.delete() ================================================ FILE: apps/knowledge/serializers/problem.py ================================================ import os from functools import reduce from typing import Dict, List import uuid_utils.compat as uuid from django.db import transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.db.search import native_search, native_page_search from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from knowledge.models import Problem, ProblemParagraphMapping, Paragraph, Knowledge, SourceType from knowledge.serializers.common import get_embedding_model_id_by_knowledge_id from knowledge.task.embedding import delete_embedding_by_source_ids, update_problem_embedding, embedding_by_data_list from maxkb.const import PROJECT_DIR class ProblemSerializer(serializers.ModelSerializer): class Meta: model = Problem fields = ['id', 'content', 'knowledge_id', 'create_time', 'update_time'] class ProblemInstanceSerializer(serializers.Serializer): id = serializers.CharField(required=False, label=_('problem id')) content = serializers.CharField(required=True, max_length=256, label=_('content')) class ProblemEditSerializer(serializers.Serializer): content = serializers.CharField(required=True, max_length=256, label=_('content')) class ProblemMappingSerializer(serializers.Serializer): paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) document_id = serializers.UUIDField(required=True, label=_('document id')) class ProblemBatchSerializer(serializers.Serializer): problem_list = serializers.ListField(required=True, label=_('problem list'), child=serializers.CharField(required=True, max_length=256, label=_('problem'))) class ProblemBatchDeleteSerializer(serializers.Serializer): problem_id_list = serializers.ListField(required=True, label=_('problem id list'), child=serializers.UUIDField(required=True, label=_('problem id'))) class AssociationParagraph(serializers.Serializer): paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id')) document_id = serializers.UUIDField(required=True, label=_('document id')) class BatchAssociation(serializers.Serializer): problem_id_list = serializers.ListField(required=True, label=_('problem id list'), child=serializers.UUIDField(required=True, label=_('problem id'))) paragraph_list = AssociationParagraph(many=True) class ProblemSerializers(serializers.Serializer): class BatchOperate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def delete(self, problem_id_list: List, with_valid=True): if with_valid: self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter( knowledge_id=knowledge_id, problem_id__in=problem_id_list) source_ids = [row.id for row in problem_paragraph_mapping_list] problem_paragraph_mapping_list.delete() QuerySet(Problem).filter(id__in=problem_id_list).delete() delete_embedding_by_source_ids(source_ids) return True def association(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) BatchAssociation(data=instance).is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') paragraph_list = instance.get('paragraph_list') problem_id_list = instance.get('problem_id_list') problem_list = QuerySet(Problem).filter(id__in=problem_id_list) exits_problem_paragraph_mapping = QuerySet( ProblemParagraphMapping ).filter(problem_id__in=problem_id_list, paragraph_id__in=[p.get('paragraph_id') for p in paragraph_list]) problem_paragraph_mapping_list = [ (problem_paragraph_mapping, problem) for problem_paragraph_mapping, problem in reduce( lambda x, y: [*x, *y], [ [ to_problem_paragraph_mapping( problem, paragraph.get('document_id'), paragraph.get('paragraph_id'), knowledge_id ) for paragraph in paragraph_list ] for problem in problem_list ], [] ) if not is_exits(exits_problem_paragraph_mapping, problem_paragraph_mapping) ] QuerySet(ProblemParagraphMapping).bulk_create( [problem_paragraph_mapping for problem_paragraph_mapping, problem in problem_paragraph_mapping_list] ) data_list = [ { 'text': problem.content, 'is_active': True, 'source_type': SourceType.PROBLEM, 'source_id': str(problem_paragraph_mapping.id), 'document_id': str(problem_paragraph_mapping.document_id), 'paragraph_id': str(problem_paragraph_mapping.paragraph_id), 'knowledge_id': knowledge_id, } for problem_paragraph_mapping, problem in problem_paragraph_mapping_list ] model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id')) embedding_by_data_list(data_list, model_id=model_id) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) problem_id = serializers.UUIDField(required=True, label=_('problem id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def list_paragraph(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( knowledge_id=self.data.get("knowledge_id"), problem_id=self.data.get("problem_id") ) if problem_paragraph_mapping is None or len(problem_paragraph_mapping) == 0: return [] return native_search( QuerySet(Paragraph).filter(id__in=[row.paragraph_id for row in problem_paragraph_mapping]), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_paragraph.sql'))) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return ProblemInstanceSerializer(QuerySet(Problem).get(**{'id': self.data.get('problem_id')})).data @transaction.atomic def delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter( knowledge_id=self.data.get('knowledge_id'), problem_id=self.data.get('problem_id')) source_ids = [row.id for row in problem_paragraph_mapping_list] problem_paragraph_mapping_list.delete() QuerySet(Problem).filter(id=self.data.get('problem_id')).delete() delete_embedding_by_source_ids(source_ids) return True @transaction.atomic def edit(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) problem_id = self.data.get('problem_id') knowledge_id = self.data.get('knowledge_id') content = instance.get('content') problem = QuerySet(Problem).filter(id=problem_id, knowledge_id=knowledge_id).first() QuerySet(Knowledge).filter(id=knowledge_id) problem.content = content problem.save() model_id = get_embedding_model_id_by_knowledge_id(knowledge_id) update_problem_embedding(problem_id, content, model_id) class Create(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def batch(self, problem_list, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ProblemBatchSerializer(data={'problem_list': problem_list}).is_valid(raise_exception=True) problem_list = list(set(problem_list)) knowledge_id = self.data.get('knowledge_id') exists_problem_content_list = [ problem.content for problem in QuerySet( Problem ).filter(knowledge_id=knowledge_id, content__in=problem_list) ] problem_instance_list = [ Problem( id=uuid.uuid7(), knowledge_id=knowledge_id, content=problem_content ) for problem_content in problem_list if ( not exists_problem_content_list.__contains__( problem_content ) if len(exists_problem_content_list) > 0 else True ) ] QuerySet(Problem).bulk_create(problem_instance_list) if len(problem_instance_list) > 0 else None return [ProblemSerializer(problem_instance).data for problem_instance in problem_instance_list] class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) content = serializers.CharField(required=False, label=_('content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def get_query_set(self): self.is_valid() query_set = QuerySet(model=Problem) query_set = query_set.filter( **{'knowledge_id': self.data.get('knowledge_id')}) if 'content' in self.data: query_set = query_set.filter(**{'content__icontains': self.data.get('content')}) query_set = query_set.order_by("-create_time") return query_set def list(self): query_set = self.get_query_set() return native_search(query_set, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem.sql'))) def page(self, current_page, page_size): query_set = self.get_query_set() return native_page_search(current_page, page_size, query_set, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'list_problem.sql'))) def is_exits(exits_problem_paragraph_mapping_list, new_paragraph_mapping): filter_list = [exits_problem_paragraph_mapping for exits_problem_paragraph_mapping in exits_problem_paragraph_mapping_list if str(exits_problem_paragraph_mapping.paragraph_id) == new_paragraph_mapping.paragraph_id and str(exits_problem_paragraph_mapping.problem_id) == new_paragraph_mapping.problem_id and str(exits_problem_paragraph_mapping.knowledge_id) == new_paragraph_mapping.knowledge_id] return len(filter_list) > 0 def to_problem_paragraph_mapping(problem, document_id: str, paragraph_id: str, knowledge_id: str): return ProblemParagraphMapping( id=uuid.uuid7(), document_id=document_id, paragraph_id=paragraph_id, knowledge_id=knowledge_id, problem_id=str(problem.id) ), problem ================================================ FILE: apps/knowledge/serializers/tag.py ================================================ # coding=utf-8 """ @project: maxkb @Author:AI Assistant @file: tag.py @date:2025/10/13 @desc: 标签系统相关序列化器 """ from collections import defaultdict from typing import Dict import uuid_utils.compat as uuid from django.db import transaction from django.db.models import QuerySet from django.db.models.aggregates import Count from django.db.models.query_utils import Q from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.exception.app_exception import AppApiException from knowledge.models import Tag, Knowledge, DocumentTag class TagModelSerializer(serializers.ModelSerializer): """标签模型序列化器""" class Meta: model = Tag fields = ['id', 'knowledge_id', 'key', 'value', 'create_time', 'update_time'] read_only_fields = ['id', 'create_time', 'update_time'] class TagCreateSerializer(serializers.Serializer): """创建标签序列化器""" key = serializers.CharField(required=True, max_length=64, label=_('Tag Key')) value = serializers.CharField(required=True, max_length=128, label=_('Tag Value')) class TagEditSerializer(serializers.Serializer): key = serializers.CharField(required=False, max_length=64, label=_('Tag Key')) value = serializers.CharField(required=False, max_length=128, label=_('Tag Value')) class TagSerializers(serializers.Serializer): class Create(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('Workspace ID')) knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID')) tags = serializers.ListField(required=True, label=_('Tags'), child=TagCreateSerializer()) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def insert(self): self.is_valid(raise_exception=True) knowledge_id = self.data.get('knowledge_id') # 获取数据库中已存在的key-value组合 existing_tags = set( QuerySet(Tag).filter(knowledge_id=knowledge_id) .values_list('key', 'value', named=False) ) # 过滤掉已存在的标签 tag_objects = [] for tag_data in self.data.get('tags', []): key = tag_data.get('key') value = tag_data.get('value') # 检查key-value组合是否已存在 if (key, value) not in existing_tags: tag = Tag( id=uuid.uuid7(), knowledge_id=knowledge_id, key=key, value=value ) tag_objects.append(tag) # 将新标签添加到已存在集合中,避免本次批量插入中的重复 existing_tags.add((key, value)) # 批量插入未重复的标签 if tag_objects: Tag.objects.bulk_create(tag_objects) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('Workspace ID')) knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID')) tag_id = serializers.UUIDField(required=True, label=_('Tag ID')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def edit(self, instance: Dict): self.is_valid(raise_exception=True) tag = QuerySet(Tag).get(id=self.data.get('tag_id')) if tag is None: raise AppApiException(500, _('Tag id does not exist')) # 如果key发生变化,更新所有相同key的标签 if instance.get('key') and instance.get('key') != tag.key: old_key = tag.key new_key = instance.get('key') # 检查新key是否已存在于同一个knowledge中 existing_key_exists = QuerySet(Tag).filter( knowledge_id=tag.knowledge_id, key=new_key ).exists() if existing_key_exists: raise AppApiException(500, _('Tag key already exists')) # 批量更新所有具有相同old_key的标签 QuerySet(Tag).filter( knowledge_id=tag.knowledge_id, key=old_key ).update(key=new_key) # 如果只是value变化,只更新当前标签 if instance.get('value') and instance.get('value') != tag.value: # 检查新key是否已存在于同一个knowledge中 existing_value_exists = QuerySet(Tag).filter( knowledge_id=tag.knowledge_id, key=instance.get('key'), value=instance.get('value') ).exists() if existing_value_exists: raise AppApiException(500, _('Tag value already exists')) QuerySet(Tag).filter( id=tag.id ).update(value=instance.get('value')) @transaction.atomic def delete(self, delete_type: str): self.is_valid(raise_exception=True) if delete_type == 'key': # 删除同一knowledge_id下相同key的所有标签 tag = QuerySet(Tag).get(id=self.data.get('tag_id')) if tag is None: raise AppApiException(500, _('Tag id does not exist')) QuerySet(Tag).filter( knowledge_id=tag.knowledge_id, key=tag.key ).delete() QuerySet(DocumentTag).filter(tag_id=tag.id).delete() else: # 仅删除当前标签 QuerySet(Tag).filter(id=self.data.get('tag_id')).delete() QuerySet(DocumentTag).filter(tag_id=self.data.get('tag_id')).delete() class BatchDelete(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('Workspace ID')) knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID')) tag_ids = serializers.ListField(required=True, label=_('Tag IDs'), child=serializers.UUIDField()) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) @transaction.atomic def batch_delete(self): self.is_valid(raise_exception=True) tag_ids = self.data.get('tag_ids', []) if not tag_ids: return # 获取要删除的标签的key tags_to_delete = QuerySet(Tag).filter(id__in=tag_ids) keys_to_delete = set(tags_to_delete.values_list('key', flat=True)) # 删除具有相同key的所有标签 QuerySet(Tag).filter( knowledge_id=self.data.get('knowledge_id'), key__in=keys_to_delete ).delete() # 删除关联的DocumentTag QuerySet(DocumentTag).filter(tag_id__in=tag_ids).delete() class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('Workspace ID')) knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID')) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')) if workspace_id and workspace_id != 'None': query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Knowledge id does not exist')) def list(self): self.is_valid(raise_exception=True) if self.data.get('name'): name = self.data.get('name') tags = QuerySet(Tag).filter( knowledge_id=self.data.get('knowledge_id') ).filter( Q(key__icontains=name) | Q(value__icontains=name) ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value') else: # 获取所有标签,按创建时间排序保持稳定顺序 tags = QuerySet(Tag).filter( knowledge_id=self.data.get('knowledge_id') ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value') tag_ids = [tag['id'] for tag in tags] tag_doc_count_map = {row['tag_id']: row['doc_count'] for row in QuerySet(DocumentTag).filter(tag_id__in=tag_ids) .values('tag_id').annotate(doc_count=Count('document_id')) } # 按key分组 grouped_tags = defaultdict(list) for tag in tags: grouped_tags[tag['key']].append({ 'id': tag['id'], 'value': tag['value'], 'doc_count': tag_doc_count_map.get(tag['id'],0), 'create_time': tag['create_time'], 'update_time': tag['update_time'] }) # 转换为期望的格式,保持key的顺序 result = [] # 按key排序以确保结果顺序一致 for key in sorted(grouped_tags.keys()): values = grouped_tags[key] # 按创建时间对values进行排序 values.sort(key=lambda x: x['create_time']) result.append({ 'key': key, 'values': values, }) return result ================================================ FILE: apps/knowledge/sql/blend_search.sql ================================================ SELECT paragraph_id, comprehensive_score, comprehensive_score AS similarity FROM ( SELECT DISTINCT ON ( "paragraph_id" ) ( 1 - distance + ts_similarity ) as similarity, *, (1 - distance + ts_similarity) AS comprehensive_score FROM ( SELECT *, (embedding.embedding::vector(%s) <=> %s) as distance, (ts_rank_cd( embedding.search_vector, websearch_to_tsquery('simple', %s ), 32 )) AS ts_similarity FROM embedding ${embedding_query} ORDER BY distance ) TEMP ORDER BY paragraph_id, similarity DESC ) DISTINCT_TEMP WHERE comprehensive_score >%s ORDER BY comprehensive_score DESC LIMIT %s ================================================ FILE: apps/knowledge/sql/embedding_search.sql ================================================ SELECT paragraph_id, comprehensive_score, comprehensive_score as similarity FROM ( SELECT DISTINCT ON ("paragraph_id") ( 1 - distance ),* ,(1 - distance) AS comprehensive_score FROM ( SELECT *, ( embedding.embedding::vector(%s) <=> %s ) AS distance FROM embedding ${embedding_query} ORDER BY distance) TEMP ORDER BY paragraph_id, distance ) DISTINCT_TEMP WHERE comprehensive_score>%s ORDER BY comprehensive_score DESC LIMIT %s ================================================ FILE: apps/knowledge/sql/hit_test.sql ================================================ SELECT paragraph_id, comprehensive_score, comprehensive_score as similarity FROM ( SELECT DISTINCT ON ("paragraph_id") ( similarity ),* ,similarity AS comprehensive_score FROM ( SELECT *, ( 1 - ( embedding.embedding <=> %s ) ) AS similarity FROM embedding ${embedding_query} ) TEMP ORDER BY paragraph_id, similarity DESC ) DISTINCT_TEMP WHERE comprehensive_score>%s ORDER BY comprehensive_score DESC LIMIT %s ================================================ FILE: apps/knowledge/sql/keywords_search.sql ================================================ SELECT paragraph_id, comprehensive_score, comprehensive_score as similarity FROM ( SELECT DISTINCT ON ("paragraph_id") ( similarity ),* ,similarity AS comprehensive_score FROM ( SELECT *,ts_rank_cd(embedding.search_vector,websearch_to_tsquery('simple',%s),32) AS similarity FROM embedding ${keywords_query}) TEMP ORDER BY paragraph_id, similarity DESC ) DISTINCT_TEMP WHERE comprehensive_score>%s ORDER BY comprehensive_score DESC LIMIT %s ================================================ FILE: apps/knowledge/sql/list_document.sql ================================================ SELECT * from ( SELECT "document".*, to_json("document"."meta") as meta, to_json("document"."status_meta") as status_meta, (SELECT "count"("id") FROM "paragraph" WHERE document_id = "document"."id") as "paragraph_count", tag_agg.tag_count as "tag_count", COALESCE(tag_agg.tags, '[]'::json) as "tags" FROM "document" "document" LEFT JOIN LATERAL ( SELECT COUNT(*)::int as tag_count, json_agg( json_build_object( 'id', "tag"."id", 'key', "tag"."key", 'value', "tag"."value" ) ORDER BY "tag"."key", "tag"."value" ) as tags FROM "document_tag" "document_tag" INNER JOIN "tag" "tag" ON "tag"."id" = "document_tag"."tag_id" WHERE "document_tag"."document_id" = "document"."id" ) tag_agg ON TRUE ${document_custom_sql} ) temp ${order_by_query} ================================================ FILE: apps/knowledge/sql/list_knowledge.sql ================================================ SELECT * FROM (SELECT "temp_knowledge".id::text, "temp_knowledge".name, "temp_knowledge".desc, "temp_knowledge".type, 'knowledge' as resource_type, "temp_knowledge".workspace_id, "temp_knowledge".folder_id, "temp_knowledge".user_id, "user"."nick_name" as nick_name, "temp_knowledge".create_time, "temp_knowledge".update_time, "temp_knowledge".file_size_limit, "temp_knowledge".file_count_limit, "temp_knowledge"."scope", "temp_knowledge"."embedding_model_id"::text, "document_temp"."char_length", to_json("temp_knowledge".meta)::jsonb as meta, CASE WHEN "app_knowledge_temp"."count" IS NULL THEN 0 ELSE "app_knowledge_temp"."count" END AS application_mapping_count, "document_temp".document_count FROM (SELECT knowledge.* FROM knowledge knowledge ${knowledge_custom_sql}) temp_knowledge LEFT JOIN (SELECT "count"("id") AS document_count, "sum"("char_length") "char_length", knowledge_id FROM "document" GROUP BY knowledge_id) "document_temp" ON temp_knowledge."id" = "document_temp".knowledge_id LEFT JOIN (SELECT "count"("id"), knowledge_id FROM application_knowledge_mapping GROUP BY knowledge_id) app_knowledge_temp ON temp_knowledge."id" = "app_knowledge_temp".knowledge_id left join "user" on "user".id = temp_knowledge.user_id ) temp ${default_sql} ================================================ FILE: apps/knowledge/sql/list_knowledge_application.sql ================================================ SELECT * FROM application WHERE user_id = %s UNION SELECT * FROM application WHERE "id"::text in (select target from workspace_user_resource_permission where auth_target_type = 'APPLICATION' and 'VIEW' = any (permission_list)) ================================================ FILE: apps/knowledge/sql/list_knowledge_user.sql ================================================ SELECT * FROM (SELECT "temp_knowledge".id::text, "temp_knowledge".name, "temp_knowledge".desc, "temp_knowledge".type, 'knowledge' as resource_type, "temp_knowledge".workspace_id, "temp_knowledge".folder_id, "temp_knowledge".user_id, "user"."nick_name" as nick_name, "temp_knowledge".create_time, "temp_knowledge".update_time, "temp_knowledge".file_size_limit, "temp_knowledge".file_count_limit, "temp_knowledge"."scope", "temp_knowledge"."embedding_model_id"::text, "document_temp"."char_length", to_json("temp_knowledge".meta)::jsonb as meta, CASE WHEN "app_knowledge_temp"."count" IS NULL THEN 0 ELSE "app_knowledge_temp"."count" END AS application_mapping_count, "document_temp".document_count FROM (SELECT knowledge.* FROM knowledge knowledge ${knowledge_custom_sql} AND id::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and 'VIEW' = any (permission_list))) temp_knowledge LEFT JOIN (SELECT "count"("id") AS document_count, "sum"("char_length") "char_length", knowledge_id FROM "document" GROUP BY knowledge_id) "document_temp" ON temp_knowledge."id" = "document_temp".knowledge_id LEFT JOIN (SELECT "count"("id"), knowledge_id FROM application_knowledge_mapping GROUP BY knowledge_id) app_knowledge_temp ON temp_knowledge."id" = "app_knowledge_temp".knowledge_id left join "user" on "user".id = temp_knowledge.user_id ) temp ${default_sql} ================================================ FILE: apps/knowledge/sql/list_knowledge_user_ee.sql ================================================ SELECT * FROM (SELECT "temp_knowledge".id::text, "temp_knowledge".name, "temp_knowledge".desc, "temp_knowledge".type, 'knowledge' as resource_type, "temp_knowledge".workspace_id, "temp_knowledge".folder_id, "temp_knowledge".user_id, "user"."nick_name" as nick_name, "temp_knowledge".create_time, "temp_knowledge".update_time, "temp_knowledge".file_size_limit, "temp_knowledge".file_count_limit, "temp_knowledge"."scope", "temp_knowledge"."embedding_model_id"::text, "document_temp"."char_length", to_json("temp_knowledge".meta)::jsonb as meta, CASE WHEN "app_knowledge_temp"."count" IS NULL THEN 0 ELSE "app_knowledge_temp"."count" END AS application_mapping_count, "document_temp".document_count FROM (SELECT knowledge.* FROM knowledge knowledge ${knowledge_custom_sql} AND "knowledge".id::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and case when auth_type = 'ROLE' then 'ROLE' = any (permission_list) and 'KNOWLEDGE:READ' in (select (case when user_role_relation.role_id = any (array ['USER']) THEN 'KNOWLEDGE:READ' else role_permission.permission_id END) from role_permission role_permission right join user_role_relation user_role_relation on user_role_relation.role_id=role_permission.role_id where user_role_relation.user_id=workspace_user_resource_permission.user_id and user_role_relation.workspace_id=workspace_user_resource_permission.workspace_id) else 'VIEW' = any (permission_list) end )) temp_knowledge LEFT JOIN (SELECT "count"("id") AS document_count, "sum"("char_length") "char_length", knowledge_id FROM "document" GROUP BY knowledge_id) "document_temp" ON temp_knowledge."id" = "document_temp".knowledge_id LEFT JOIN (SELECT "count"("id"), knowledge_id FROM application_knowledge_mapping GROUP BY knowledge_id) app_knowledge_temp ON temp_knowledge."id" = "app_knowledge_temp".knowledge_id left join "user" on "user".id = temp_knowledge.user_id ) temp ${default_sql} ================================================ FILE: apps/knowledge/sql/list_paragraph.sql ================================================ SELECT (SELECT "name" FROM "document" WHERE "id"=document_id) as document_name, (SELECT "name" FROM "knowledge" WHERE "id"=knowledge_id) as knowledge_name, * FROM "paragraph" ================================================ FILE: apps/knowledge/sql/list_paragraph_document_name.sql ================================================ SELECT (SELECT "name" FROM "document" WHERE "id"=document_id) as document_name, * FROM "paragraph" ================================================ FILE: apps/knowledge/sql/list_problem.sql ================================================ SELECT problem.*, (SELECT COUNT(ppm.id) FROM problem_paragraph_mapping ppm INNER JOIN paragraph p ON ppm.paragraph_id = p.id WHERE ppm.problem_id = problem.id) AS "paragraph_count" FROM problem problem ================================================ FILE: apps/knowledge/sql/list_problem_mapping.sql ================================================ SELECT "problem"."content",problem_paragraph_mapping.paragraph_id FROM problem problem LEFT JOIN problem_paragraph_mapping problem_paragraph_mapping ON problem_paragraph_mapping.problem_id=problem."id" ================================================ FILE: apps/knowledge/sql/update_document_char_length.sql ================================================ UPDATE "document" SET "char_length" = ( SELECT CASE WHEN "sum" ( "char_length" ( "content" ) ) IS NULL THEN 0 ELSE "sum" ( "char_length" ( "content" ) ) END FROM paragraph WHERE "document_id" = %s ) WHERE "id" = %s ================================================ FILE: apps/knowledge/sql/update_document_status_meta.sql ================================================ UPDATE "document" "document" SET status_meta = jsonb_set ( "document".status_meta, '{aggs}', tmp.status_meta ) FROM ( SELECT COALESCE ( jsonb_agg ( jsonb_delete ( ( row_to_json ( record ) :: JSONB ), 'document_id' ) ), '[]' :: JSONB ) AS status_meta, document_id AS document_id FROM ( SELECT "paragraph".status, "count" ( "paragraph"."id" ), "document"."id" AS document_id FROM "document" "document" LEFT JOIN "paragraph" "paragraph" ON "document"."id" = paragraph.document_id ${document_custom_sql} GROUP BY "paragraph".status, "document"."id" ) record GROUP BY document_id ) tmp WHERE "document".id="tmp".document_id ================================================ FILE: apps/knowledge/sql/update_paragraph_status.sql ================================================ UPDATE "${table_name}" SET status = reverse ( SUBSTRING ( reverse ( LPAD( status, ${bit_number}, 'n' ) ) :: TEXT FROM 1 FOR ${up_index} ) || ${status_number} || SUBSTRING ( reverse ( LPAD( status, ${bit_number}, 'n' ) ) :: TEXT FROM ${next_index} ) ), status_meta = jsonb_set ( "${table_name}".status_meta, '{state_time,${current_index}}', jsonb_set ( COALESCE ( "${table_name}".status_meta #> '{state_time,${current_index}}', jsonb_build_object ( '${status_number}', '${current_time}' ) ), '{${status_number}}', CONCAT ( '"', '${current_time}', '"' ) :: JSONB ) ) ================================================ FILE: apps/knowledge/task/__init__.py ================================================ ================================================ FILE: apps/knowledge/task/embedding.py ================================================ # coding=utf-8 import traceback from typing import List from celery_once import QueueOnce from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from common.config.embedding_config import ModelManage from common.event.listener_manage import ListenerManagement, UpdateProblemArgs, UpdateEmbeddingKnowledgeIdArgs, \ UpdateEmbeddingDocumentIdArgs from common.utils.logger import maxkb_logger from knowledge.models import Document, TaskType, State from knowledge.serializers.common import drop_knowledge_index from models_provider.models import Model from models_provider.tools import get_model, get_model_default_params from ops import celery_app def get_embedding_model(model_id, exception_handler=lambda e: maxkb_logger.error( _('Failed to obtain vector model: {error} {traceback}').format( error=str(e), traceback=traceback.format_exc() ))): try: model = QuerySet(Model).filter(id=model_id).first() default_params = get_model_default_params(model) embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model, **{**default_params})) except Exception as e: exception_handler(e) raise e return embedding_model @celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id']}, name='celery:embedding_by_paragraph') def embedding_by_paragraph(paragraph_id, model_id): embedding_model = get_embedding_model(model_id) ListenerManagement.embedding_by_paragraph(paragraph_id, embedding_model) @celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']}, name='celery:embedding_by_paragraph_data_list') def embedding_by_paragraph_data_list(data_list, paragraph_id_list, model_id): embedding_model = get_embedding_model(model_id) ListenerManagement.embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model) @celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']}, name='celery:embedding_by_paragraph_list') def embedding_by_paragraph_list(paragraph_id_list, model_id): embedding_model = get_embedding_model(model_id) ListenerManagement.embedding_by_paragraph_list(paragraph_id_list, embedding_model) @celery_app.task(base=QueueOnce, once={'keys': ['document_id']}, name='celery:embedding_by_document') def embedding_by_document(document_id, model_id, state_list=None): """ 向量化文档 @param state_list: @param document_id: 文档id @param model_id 向量模型 :return: None """ if state_list is None: state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value, State.REVOKE.value, State.REVOKED.value, State.IGNORED.value] def exception_handler(e): ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.FAILURE) maxkb_logger.error( _('Failed to obtain vector model: {error} {traceback}').format( error=str(e), traceback=traceback.format_exc() )) embedding_model = get_embedding_model(model_id, exception_handler) # ListenerManagement.embedding_by_document(document_id, embedding_model, state_list) @celery_app.task(name='celery:embedding_by_document_list') def embedding_by_document_list(document_id_list, model_id): """ 向量化文档 @param document_id_list: 文档id列表 @param model_id 向量模型 :return: None """ for document_id in document_id_list: embedding_by_document.delay(document_id, model_id) @celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:embedding_by_knowledge') def embedding_by_knowledge(knowledge_id, model_id): """ 向量化知识库 @param knowledge_id: 知识库id @param model_id 向量模型 :return: None """ maxkb_logger.info(_('Start--->Vectorized knowledge: {knowledge_id}').format(knowledge_id=knowledge_id)) try: ListenerManagement.delete_embedding_by_knowledge(knowledge_id) drop_knowledge_index(knowledge_id=knowledge_id) document_list = QuerySet(Document).filter(knowledge_id=knowledge_id) maxkb_logger.info(_('Knowledge documentation: {document_names}').format( document_names=", ".join([d.name for d in document_list]))) for document in document_list: try: embedding_by_document.delay(document.id, model_id) except Exception as e: pass except Exception as e: maxkb_logger.error( _('Vectorized knowledge: {knowledge_id} error {error} {traceback}'.format(knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc()))) finally: maxkb_logger.info(_('End--->Vectorized knowledge: {knowledge_id}').format(knowledge_id=knowledge_id)) def embedding_by_problem(args, model_id): """ 向量话问题 @param args: 问题对象 @param model_id: 模型id @return: """ embedding_model = get_embedding_model(model_id) ListenerManagement.embedding_by_problem(args, embedding_model) def embedding_by_data_list(args: List, model_id): embedding_model = get_embedding_model(model_id) ListenerManagement.embedding_by_data_list(args, embedding_model) def delete_embedding_by_document(document_id): """ 删除指定文档id的向量 @param document_id: 文档id @return: None """ ListenerManagement.delete_embedding_by_document(document_id) def delete_embedding_by_document_list(document_id_list: List[str]): """ 删除指定文档列表的向量数据 @param document_id_list: 文档id列表 @return: None """ ListenerManagement.delete_embedding_by_document_list(document_id_list) def delete_embedding_by_knowledge(knowledge_id): """ 删除指定数据集向量数据 @param knowledge_id: 数据集id @return: None """ ListenerManagement.delete_embedding_by_knowledge(knowledge_id) def delete_embedding_by_paragraph(paragraph_id): """ 删除指定段落的向量数据 @param paragraph_id: 段落id @return: None """ ListenerManagement.delete_embedding_by_paragraph(paragraph_id) def delete_embedding_by_source(source_id): """ 删除指定资源id的向量数据 @param source_id: 资源id @return: None """ ListenerManagement.delete_embedding_by_source(source_id) def disable_embedding_by_paragraph(paragraph_id): """ 禁用某个段落id的向量 @param paragraph_id: 段落id @return: None """ ListenerManagement.disable_embedding_by_paragraph(paragraph_id) def enable_embedding_by_paragraph(paragraph_id): """ 开启某个段落id的向量数据 @param paragraph_id: 段落id @return: None """ ListenerManagement.enable_embedding_by_paragraph(paragraph_id) def delete_embedding_by_source_ids(source_ids: List[str]): """ 删除向量根据source_id_list @param source_ids: @return: """ ListenerManagement.delete_embedding_by_source_ids(source_ids) def update_problem_embedding(problem_id: str, problem_content: str, model_id): """ 更新问题 @param problem_id: @param problem_content: @param model_id: @return: """ model = get_embedding_model(model_id) ListenerManagement.update_problem(UpdateProblemArgs(problem_id, problem_content, model)) def update_embedding_knowledge_id(paragraph_id_list, target_knowledge_id): """ 修改向量数据到指定知识库 @param paragraph_id_list: 指定段落的向量数据 @param target_knowledge_id: 知识库id @return: """ ListenerManagement.update_embedding_knowledge_id( UpdateEmbeddingKnowledgeIdArgs(paragraph_id_list, target_knowledge_id)) def delete_embedding_by_paragraph_ids(paragraph_ids: List[str]): """ 删除指定段落列表的向量数据 @param paragraph_ids: 段落列表 @return: None """ ListenerManagement.delete_embedding_by_paragraph_ids(paragraph_ids) def update_embedding_document_id(paragraph_id_list, target_document_id, target_knowledge_id, target_embedding_model_id=None): target_embedding_model = get_embedding_model( target_embedding_model_id) if target_embedding_model_id is not None else None ListenerManagement.update_embedding_document_id( UpdateEmbeddingDocumentIdArgs(paragraph_id_list, target_document_id, target_knowledge_id, target_embedding_model)) def delete_embedding_by_knowledge_id_list(knowledge_id_list): ListenerManagement.delete_embedding_by_knowledge_id_list(knowledge_id_list) ================================================ FILE: apps/knowledge/task/generate.py ================================================ import traceback from celery_once import QueueOnce from django.db.models import QuerySet from django.db.models.functions import Reverse, Substr from django.utils.translation import gettext_lazy as _ from langchain_core.messages import HumanMessage from common.config.embedding_config import ModelManage from common.event.listener_manage import ListenerManagement from common.utils.logger import maxkb_logger from common.utils.page_utils import page, page_desc from knowledge.models import Paragraph, Document, Status, TaskType, State from knowledge.task.handler import save_problem from models_provider.models import Model from models_provider.tools import get_model from ops import celery_app def get_llm_model(model_id, model_params_setting=None): model = QuerySet(Model).filter(id=model_id).first() return ModelManage.get_model(model_id, lambda _id: get_model(model, **(model_params_setting or {}))) def generate_problem_by_paragraph(paragraph, llm_model, prompt): try: ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM, State.STARTED) res = llm_model.invoke( [HumanMessage(content=prompt.replace('{data}', paragraph.content).replace('{title}', paragraph.title))]) if (res.content is None) or (len(res.content) == 0): return problems = res.content.split('\n') for problem in problems: save_problem(paragraph.knowledge_id, paragraph.document_id, paragraph.id, problem) ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM, State.SUCCESS) except Exception as e: ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM, State.FAILURE) def get_generate_problem(llm_model, prompt, post_apply=lambda: None, is_the_task_interrupted=lambda: False): def generate_problem(paragraph_list): for paragraph in paragraph_list: if is_the_task_interrupted(): return generate_problem_by_paragraph(paragraph, llm_model, prompt) post_apply() return generate_problem def get_is_the_task_interrupted(document_id): def is_the_task_interrupted(): document = QuerySet(Document).filter(id=document_id).first() if document is None or Status(document.status)[TaskType.GENERATE_PROBLEM] == State.REVOKE: return True return False return is_the_task_interrupted @celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:generate_related_by_knowledge') def generate_related_by_knowledge_id(knowledge_id, model_id, model_params_setting, prompt, state_list=None): document_list = QuerySet(Document).filter(knowledge_id=knowledge_id) for document in document_list: try: generate_related_by_document_id.delay(document.id, model_id, model_params_setting, prompt, state_list) except Exception as e: pass @celery_app.task(base=QueueOnce, once={'keys': ['document_id']}, name='celery:generate_related_by_document') def generate_related_by_document_id(document_id, model_id, model_params_setting, prompt, state_list=None): if state_list is None: state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value, State.REVOKE.value, State.REVOKED.value, State.IGNORED.value] try: is_the_task_interrupted = get_is_the_task_interrupted(document_id) if is_the_task_interrupted(): return ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.GENERATE_PROBLEM, State.STARTED) llm_model = get_llm_model(model_id, model_params_setting) # 生成问题函数 generate_problem = get_generate_problem(llm_model, prompt, ListenerManagement.get_aggregation_document_status( document_id), is_the_task_interrupted) query_set = QuerySet(Paragraph).annotate( reversed_status=Reverse('status'), task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value, 1), ).filter(task_type_status__in=state_list, document_id=document_id) page_desc(query_set, 10, generate_problem, is_the_task_interrupted) except Exception as e: maxkb_logger.error(f'根据文档生成问题:{document_id}出现错误{str(e)}{traceback.format_exc()}') maxkb_logger.error(_('Generate issue based on document: {document_id} error {error}{traceback}').format( document_id=document_id, error=str(e), traceback=traceback.format_exc())) finally: ListenerManagement.post_update_document_status(document_id, TaskType.GENERATE_PROBLEM) maxkb_logger.info(_('End--->Generate problem: {document_id}').format(document_id=document_id)) @celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']}, name='celery:generate_related_by_paragraph_list') def generate_related_by_paragraph_id_list(document_id, paragraph_id_list, model_id, model_params_setting, prompt): try: is_the_task_interrupted = get_is_the_task_interrupted(document_id) if is_the_task_interrupted(): ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.GENERATE_PROBLEM, State.REVOKED) return ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.GENERATE_PROBLEM, State.STARTED) llm_model = get_llm_model(model_id, model_params_setting) # 生成问题函数 generate_problem = get_generate_problem(llm_model, prompt, ListenerManagement.get_aggregation_document_status( document_id)) def is_the_task_interrupted(): document = QuerySet(Document).filter(id=document_id).first() if document is None or Status(document.status)[TaskType.GENERATE_PROBLEM] == State.REVOKE: return True return False page(QuerySet(Paragraph).filter(id__in=paragraph_id_list), 10, generate_problem, is_the_task_interrupted) finally: ListenerManagement.post_update_document_status(document_id, TaskType.GENERATE_PROBLEM) ================================================ FILE: apps/knowledge/task/handler.py ================================================ # coding=utf-8 import re import traceback from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from common.utils.fork import ChildLink, Fork from common.utils.logger import maxkb_logger from common.utils.split_model import get_split_model from knowledge.models import State from knowledge.models.knowledge import KnowledgeType, Document, Knowledge def get_save_handler(knowledge_id, selector): from knowledge.serializers.document import DocumentSerializers def handler(child_link: ChildLink, response: Fork.Response): if response.status == 200: try: document_name = child_link.tag.text if child_link.tag is not None and len( child_link.tag.text.strip()) > 0 else child_link.url paragraphs = get_split_model('web.md').parse(response.content) DocumentSerializers.Create( data={'knowledge_id': knowledge_id} ).save({ 'name': document_name, 'paragraphs': paragraphs, 'meta': {'source_url': child_link.url, 'selector': selector}, 'type': KnowledgeType.WEB }, with_valid=True) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') return handler def get_sync_handler(knowledge_id): from knowledge.serializers.document import DocumentSerializers knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() def handler(child_link: ChildLink, response: Fork.Response): if response.status == 200: try: document_name = child_link.tag.text if child_link.tag is not None and len( child_link.tag.text.strip()) > 0 else child_link.url paragraphs = get_split_model('web.md').parse(response.content) first = QuerySet(Document).filter(meta__source_url=child_link.url.strip(), knowledge=knowledge).first() if first is not None: # 如果存在,使用文档同步 DocumentSerializers.Sync(data={'document_id': first.id}).sync() else: # 插入 DocumentSerializers.Create( data={'knowledge_id': knowledge.id} ).save({ 'name': document_name, 'paragraphs': paragraphs, 'meta': {'source_url': child_link.url.strip(), 'selector': knowledge.meta.get('selector')}, 'type': KnowledgeType.WEB }, with_valid=True) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') return handler def get_sync_web_document_handler(knowledge_id): from knowledge.serializers.document import DocumentSerializers def handler(source_url: str, selector, response: Fork.Response): if response.status == 200: try: paragraphs = get_split_model('web.md').parse(response.content) # 插入 DocumentSerializers.Create(data={'knowledge_id': knowledge_id}).save( {'name': source_url[0:128], 'paragraphs': paragraphs, 'meta': {'source_url': source_url, 'selector': selector}, 'type': KnowledgeType.WEB}, with_valid=True) except Exception as e: maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}') else: Document(name=source_url[0:128], knowledge_id=knowledge_id, meta={'source_url': source_url, 'selector': selector, 'allow_download': True}, type=KnowledgeType.WEB, char_length=0, status=State.FAILURE).save() return handler def save_problem(knowledge_id, document_id, paragraph_id, problem): from knowledge.serializers.paragraph import ParagraphSerializers # print(f"knowledge_id: {knowledge_id}") # print(f"document_id: {document_id}") # print(f"paragraph_id: {paragraph_id}") # print(f"problem: {problem}") problem = re.sub(r"^\d+\.\s*", "", problem) pattern = r"(.*?)" match = re.search(pattern, problem) problem = match.group(1) if match else None if problem is None or len(problem) == 0: return try: workspace_id = QuerySet(Knowledge).filter(id=knowledge_id).first().workspace_id ParagraphSerializers.Problem( data={ 'workspace_id': workspace_id, "knowledge_id": knowledge_id, 'document_id': document_id, 'paragraph_id': paragraph_id } ).save(instance={"content": problem}, with_valid=True) except Exception as e: maxkb_logger.error(_('Association problem failed {error}').format(error=str(e))) ================================================ FILE: apps/knowledge/task/sync.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: sync.py @date:2024/8/20 21:37 @desc: """ import traceback from typing import List from celery_once import QueueOnce from django.utils.translation import gettext_lazy as _ from common.utils.fork import ForkManage, Fork from common.utils.logger import maxkb_logger from ops import celery_app @celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:sync_web_knowledge') def sync_web_knowledge(knowledge_id: str, url: str, selector: str): from knowledge.task.handler import get_save_handler try: maxkb_logger.info( _('Start--->Start synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id)) ForkManage(url, selector.split(" ") if selector is not None else []).fork(2, set(), get_save_handler(knowledge_id, selector)) maxkb_logger.info(_('End--->End synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id)) except Exception as e: maxkb_logger.error(_('Synchronize web knowledge base:{knowledge_id} error{error}{traceback}').format( knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc())) @celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:sync_replace_web_knowledge') def sync_replace_web_knowledge(knowledge_id: str, url: str, selector: str): from knowledge.task.handler import get_sync_handler try: maxkb_logger.info( _('Start--->Start synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id)) ForkManage(url, selector.split(" ") if selector is not None else []).fork(2, set(), get_sync_handler(knowledge_id )) maxkb_logger.info(_('End--->End synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id)) except Exception as e: maxkb_logger.error(_('Synchronize web knowledge base:{knowledge_id} error{error}{traceback}').format( knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc())) @celery_app.task(name='celery:sync_web_document') def sync_web_document(knowledge_id, source_url_list: List[str], selector: str): from knowledge.task.handler import get_sync_web_document_handler handler = get_sync_web_document_handler(knowledge_id) for source_url in source_url_list: try: result = Fork(base_fork_url=source_url, selector_list=selector.split(' ')).fork() handler(source_url, selector, result) except Exception as e: pass ================================================ FILE: apps/knowledge/template/csv_template_en.csv ================================================ Section title (optional), Section content (required,question answer), Question (optional,one per line in the cell) MaxKB product introduction,"MaxKB is a knowledge base question-answering system based on the LLM large language model. MaxKB = Max Knowledge Base,aims to become the most powerful brain of the enterprise。Out-of-the-box: supports direct document upload、automatic crawling of online documents、automatic text splitting and vectorization、and good intelligent question-answering interactive experience;Seamless embedding: supports zero-coding and rapid embedding into third-party business systems;Multi-model support: supports docking with mainstream large models,including Ollama local private large models (such as Llama 2、Llama 3、qwen)、Tongyi Qianwen、OpenAI、Azure OpenAI、Kimi、Zhipu AI、iFlytek Spark and Baidu Qianfan large models、etc.","What is MaxKB? MaxKB product introduction Large language model supported by MaxKB MaxKB advantages" ================================================ FILE: apps/knowledge/template/csv_template_zh.csv ================================================ 分段标题(选填),分段内容(必填,问题答案)),问题(选填,单元格内一行一个) MaxKB产品介绍,"MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB = Max Knowledge Base,旨在成为企业的最强大脑。 开箱即用:支持直接上传文档、自动爬取在线文档,支持文本自动拆分、向量化,智能问答交互体验好; 无缝嵌入:支持零编码快速嵌入到第三方业务系统; 多模型支持:支持对接主流的大模型,包括 Ollama 本地私有大模型(如 Llama 2、Llama 3、qwen)、通义千问、OpenAI、Azure OpenAI、Kimi、智谱 AI、讯飞星火和百度千帆大模型等。","MaxKB是什么? MaxKB产品介绍 MaxKB支持的大语言模型 MaxKB优势" ================================================ FILE: apps/knowledge/template/csv_template_zh_Hant.csv ================================================ 分段標題(選填),分段內容(必填,問題答案)),問題(選填,單元格內一行一個) MaxKB產品介紹,"MaxKB 是一款基於 LLM 大語言模型的知識庫問答系統。MaxKB = Max Knowledge Base,旨在成為企業的最強大大腦。 開箱即用:支援直接上傳文檔、自動爬取線上文檔,支援文字自動分割、向量化,智慧問答互動體驗好; 無縫嵌入:支援零編碼快速嵌入到第三方業務系統; 多模型支援:支持對接主流的大模型,包括Ollama 本地私有大模型(如Llama 2、Llama 3、qwen)、通義千問、OpenAI、Azure OpenAI、Kimi、智譜AI、訊飛星火和百度千帆大模型等。 ","MaxKB是什麼? MaxKB產品介紹 MaxKB支援的大語言模型 MaxKB優勢" ================================================ FILE: apps/knowledge/template/table_template_en.csv ================================================ Position, Reimbursement type, First-tier city reimbursement standard (yuan), Second-tier city reimbursement standard (yuan), Third-tier city reimbursement standard (yuan) Ordinary employees, Accommodation expenses, 500, 400, 300 Department head, Accommodation fee, 600, 500, 400 Department director, Accommodation fee, 700, 600, 500 Regional general manager, Accommodation fee, 800, 700, 600 Ordinary employees, Food expenses, 50, 40, 30 Department head, Food expenses, 50, 40, 30 Department director, Food expenses, 50, 40, 30 Regional general manager, Food expenses, 50, 40, 30 Ordinary employees, Transportation expenses, 50, 40, 30 Department head, Transportation expenses, 50, 40, 30 Department director, Transportation expenses, 50, 40, 30 Regional general manager, Transportation expenses, 50, 40, 30 ================================================ FILE: apps/knowledge/template/table_template_zh.csv ================================================ 职务,报销类型,一线城市报销标准(元),二线城市报销标准(元),三线城市报销标准(元) 普通员工,住宿费,500,400,300 部门主管,住宿费,600,500,400 部门总监,住宿费,700,600,500 区域总经理,住宿费,800,700,600 普通员工,伙食费,50,40,30 部门主管,伙食费,50,40,30 部门总监,伙食费,50,40,30 区域总经理,伙食费,50,40,30 普通员工,交通费,50,40,30 部门主管,交通费,50,40,30 部门总监,交通费,50,40,30 区域总经理,交通费,50,40,30 ================================================ FILE: apps/knowledge/template/table_template_zh_Hant.csv ================================================ 職務,報銷類型,一線城市報銷標準(元),二線城市報銷標準(元),三線城市報銷標準(元) 普通員工,住宿費,500,400,300 部門主管,住宿費,600,500,400 部門總監,住宿費,700,600,500 區域總經理,住宿費,800,700,600 普通員工,伙食費,50,40,30 部門主管,伙食費,50,40,30 部門總監,伙食費,50,40,30 區域總經理,伙食費,50,40,30 普通員工,交通費,50,40,30 部門主管,交通費,50,40,30 部門總監,交通費,50,40,30 區域總經理,交通費,50,40,30 ================================================ FILE: apps/knowledge/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/knowledge/urls.py ================================================ from django.urls import path from . import views app_name = "knowledge" # @formatter:off urlpatterns = [ path('workspace/knowledge/document/template/export', views.Template.as_view()), path('workspace/knowledge/document/table_template/export', views.TableTemplate.as_view()), path('workspace/store/knowledge_template', views.KnowledgeView.StoreKnowledge.as_view()), path('workspace//knowledge', views.KnowledgeView.as_view()), path('workspace//knowledge/base', views.KnowledgeBaseView.as_view()), path('workspace//knowledge/workflow', views.KnowledgeWorkflowView.as_view()), path('workspace//knowledge/web', views.KnowledgeWebView.as_view()), path('workspace//knowledge/model', views.KnowledgeView.Model.as_view()), path('workspace//knowledge/embedding_model', views.KnowledgeView.EmbeddingModel.as_view()), path('workspace//knowledge/tags', views.KnowledgeView.Tags.as_view()), path('workspace//knowledge/', views.KnowledgeView.Operate.as_view()), path('workspace//knowledge//sync', views.KnowledgeView.SyncWeb.as_view()), path('workspace//knowledge//workflow', views.KnowledgeWorkflowView.Operate.as_view()), path('workspace//knowledge//workflow/export', views.KnowledgeWorkflowView.Export.as_view()), path('workspace//knowledge//workflow/import', views.KnowledgeWorkflowView.Import.as_view()), path('workspace//knowledge//generate_related', views.KnowledgeView.GenerateRelated.as_view()), path('workspace//knowledge//embedding', views.KnowledgeView.Embedding.as_view()), path('workspace//knowledge//hit_test', views.KnowledgeView.HitTest.as_view()), path('workspace//knowledge//export', views.KnowledgeView.Export.as_view()), path('workspace//knowledge//export_zip', views.KnowledgeView.ExportZip.as_view()), path('workspace//knowledge//transform_workflow', views.KnowledgeView.TransformWorkflow.as_view()), path('workspace//knowledge//tags', views.KnowledgeTagView.as_view()), path('workspace//knowledge//tags/batch_delete', views.KnowledgeTagView.BatchDelete.as_view()), path('workspace//knowledge//tags/', views.KnowledgeTagView.Operate.as_view()), path('workspace//knowledge//tags//', views.KnowledgeTagView.Delete.as_view()), path('workspace//knowledge//document', views.DocumentView.as_view()), path('workspace//knowledge//document/split', views.DocumentView.Split.as_view()), path('workspace//knowledge//document/split_pattern', views.DocumentView.SplitPattern.as_view()), path('workspace//knowledge//document/batch_create', views.DocumentView.BatchCreate.as_view()), path('workspace//knowledge//document/batch_sync', views.DocumentView.BatchSync.as_view()), path('workspace//knowledge//document/batch_delete', views.DocumentView.BatchDelete.as_view()), path('workspace//knowledge//document/batch_refresh', views.DocumentView.BatchRefresh.as_view()), path('workspace//knowledge//document/batch_generate_related', views.DocumentView.BatchGenerateRelated.as_view()), path('workspace//knowledge//document/batch_export', views.DocumentView.BatchExport.as_view()), path('workspace//knowledge//document/batch_export_zip', views.DocumentView.BatchExportZip.as_view()), path('workspace//knowledge//document/web', views.WebDocumentView.as_view()), path('workspace//knowledge//document/qa', views.QaDocumentView.as_view()), path('workspace//knowledge//document/table', views.TableDocumentView.as_view()), path('workspace//knowledge//document/batch_hit_handling', views.DocumentView.BatchEditHitHandling.as_view()), path('workspace//knowledge//document/batch_cancel_task', views.DocumentView.BatchCancelTask.as_view()), path('workspace//knowledge//document/batch_add_tag', views.DocumentView.BatchAddTag.as_view()), path('workspace//knowledge//document/migrate/', views.DocumentView.Migrate.as_view()), path('workspace//knowledge//document/', views.DocumentView.Operate.as_view()), path('workspace//knowledge//document//sync', views.DocumentView.SyncWeb.as_view()), path('workspace//knowledge//document//refresh', views.DocumentView.Refresh.as_view()), path('workspace//knowledge//document//cancel_task', views.DocumentView.CancelTask.as_view()), path('workspace//knowledge//document//export', views.DocumentView.Export.as_view()), path('workspace//knowledge//document//export_zip', views.DocumentView.ExportZip.as_view()), path('workspace//knowledge//document//download_source_file', views.DocumentView.DownloadSourceFile.as_view()), path('workspace//knowledge//document//replace_source_file', views.DocumentView.ReplaceSourceFile.as_view()), path('workspace//knowledge//document//tags', views.DocumentView.Tags.as_view()), path('workspace//knowledge//document//tags/batch_delete', views.DocumentView.Tags.BatchDelete.as_view()), path('workspace//knowledge//tag//docs_delete', views.DocumentView.Tags.BatchDeleteDocsTag.as_view()), path('workspace//knowledge//document//paragraph', views.ParagraphView.as_view()), path('workspace//knowledge//document//paragraph/batch_delete', views.ParagraphView.BatchDelete.as_view()), path('workspace//knowledge//document//paragraph/batch_generate_related', views.ParagraphView.BatchGenerateRelated.as_view()), path('workspace//knowledge//document//paragraph/migrate/knowledge//document/', views.ParagraphView.BatchMigrate.as_view()), path('workspace//knowledge//document//paragraph/association', views.ParagraphView.Association.as_view()), path('workspace//knowledge//document//paragraph/unassociation', views.ParagraphView.UnAssociation.as_view()), path('workspace//knowledge//document//paragraph/adjust_position', views.ParagraphView.AdjustPosition.as_view()), path('workspace//knowledge//document//paragraph/', views.ParagraphView.Operate.as_view()), path('workspace//knowledge//document//paragraph//problem', views.ParagraphView.Problem.as_view()), path('workspace//knowledge//document//paragraph//', views.ParagraphView.Page.as_view()), path('workspace//knowledge//problem', views.ProblemView.as_view()), path('workspace//knowledge//problem/batch_delete', views.ProblemView.BatchDelete.as_view()), path('workspace//knowledge//problem/batch_association', views.ProblemView.BatchAssociation.as_view()), path('workspace//knowledge//problem/', views.ProblemView.Operate.as_view()), path('workspace//knowledge//problem//paragraph', views.ProblemView.Paragraph.as_view()), path('workspace//knowledge//problem//', views.ProblemView.Page.as_view()), path('workspace//knowledge//document//', views.DocumentView.Page.as_view()), path('workspace//knowledge//', views.KnowledgeView.Page.as_view()), path('workspace//knowledge//datasource///form_list', views.KnowledgeDatasourceFormListView.as_view()), path('workspace//knowledge//datasource///', views.KnowledgeDatasourceView.as_view()), path('workspace//knowledge//publish', views.KnowledgeWorkflowView.Publish.as_view()), path('workspace//knowledge//debug', views.KnowledgeWorkflowActionView.as_view()), path('workspace//knowledge//action//', views.KnowledgeWorkflowActionView.Page.as_view()), path('workspace//knowledge//upload_document', views.KnowledgeWorkflowUploadDocumentView.as_view()), path('workspace//knowledge//action/', views.KnowledgeWorkflowActionView.Operate.as_view()), path('workspace//knowledge//action//cancel', views.KnowledgeWorkflowActionView.Cancel.as_view()), path('workspace//knowledge//mcp_tools', views.McpServers.as_view()), path('workspace//knowledge//knowledge_version', views.KnowledgeWorkflowVersionView.as_view()), path('workspace//knowledge//knowledge_version//', views.KnowledgeWorkflowVersionView.Page.as_view()), path('workspace//knowledge//knowledge_version/', views.KnowledgeWorkflowVersionView.Operate.as_view()), ] ================================================ FILE: apps/knowledge/vector/__init__.py ================================================ ================================================ FILE: apps/knowledge/vector/base_vector.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: base_vector.py @date:2023/10/18 19:16 @desc: """ import re import threading from abc import ABC, abstractmethod from functools import reduce from typing import List, Dict from langchain_core.embeddings import Embeddings from common.chunk import text_to_chunk from common.utils.common import sub_array from knowledge.models import SourceType, SearchMode lock = threading.Lock() def chunk_data(data: Dict): if str(data.get('source_type')) == str(SourceType.PARAGRAPH.value): text = data.get('text') chunk_list = data.get('chunks') if data.get('chunks') else text_to_chunk(text) return [{**data, 'text': chunk} for chunk in chunk_list] return [data] def chunk_data_list(data_list: List[Dict]): result = [chunk_data(data) for data in data_list] return reduce(lambda x, y: [*x, *y], result, []) # 预编译正则,性能更好 RE_EMOJI = re.compile( r"[\U0001F300-\U0001FAFF]" # Emoji r"|[\u2600-\u27BF]" # Dingbats / Symbols(⚓ 在这) r"|[\uFE0E\uFE0F]", # Variation Selectors flags=re.UNICODE ) RE_WHITESPACE = re.compile(r"\s+") def normalize_for_embedding(text: str) -> str: if not text: return "" text = RE_EMOJI.sub("", text) text = RE_WHITESPACE.sub(" ", text) return text.strip() class BaseVectorStore(ABC): vector_exists = False @abstractmethod def vector_is_create(self) -> bool: """ 判断向量库是否创建 :return: 是否创建向量库 """ pass @abstractmethod def vector_create(self): """ 创建 向量库 :return: """ pass def save_pre_handler(self): """ 插入前置处理器 主要是判断向量库是否创建 :return: True """ if not BaseVectorStore.vector_exists: if not self.vector_is_create(): self.vector_create() BaseVectorStore.vector_exists = True return True def save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str, source_id: str, is_active: bool, embedding: Embeddings): """ 插入向量数据 :param source_id: 资源id :param knowledge_id: 知识库id :param text: 文本 :param source_type: 资源类型 :param document_id: 文档id :param is_active: 是否禁用 :param embedding: 向量化处理器 :param paragraph_id 段落id :return: bool """ self.save_pre_handler() data = {'document_id': document_id, 'paragraph_id': paragraph_id, 'knowledge_id': knowledge_id, 'is_active': is_active, 'source_id': source_id, 'source_type': source_type, 'text': text} chunk_list = chunk_data(data) result = sub_array(chunk_list) for child_array in result: self._batch_save(child_array, embedding, lambda: False) def batch_save(self, data_list: List[Dict], embedding: Embeddings, is_the_task_interrupted): """ 批量插入 @param data_list: 数据列表 @param embedding: 向量化处理器 @param is_the_task_interrupted: 判断是否中断任务 :return: bool """ self.save_pre_handler() chunk_list = chunk_data_list(data_list) result = sub_array(chunk_list) for child_array in result: if not is_the_task_interrupted(): self._batch_save(child_array, embedding, is_the_task_interrupted) else: break @abstractmethod def _save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str, source_id: str, is_active: bool, embedding: Embeddings): pass @abstractmethod def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_the_task_interrupted): pass def search(self, query_text, knowledge_id_list: list[str], exclude_document_id_list: list[str], exclude_paragraph_list: list[str], is_active: bool, embedding: Embeddings): if knowledge_id_list is None or len(knowledge_id_list) == 0: return [] query_text = normalize_for_embedding(query_text) embedding_query = embedding.embed_query(query_text) result = self.query(embedding_query, knowledge_id_list, exclude_document_id_list, exclude_paragraph_list, is_active, 1, 3, 0.65) return result[0] @abstractmethod def query(self, query_text: str, query_embedding: List[float], knowledge_id_list: list[str], document_id_list: list[str] | None, exclude_document_id_list: list[str], exclude_paragraph_list: list[str], is_active: bool, top_n: int, similarity: float, search_mode: SearchMode): pass @abstractmethod def hit_test(self, query_text, knowledge_id: list[str], exclude_document_id_list: list[str], top_number: int, similarity: float, search_mode: SearchMode, embedding: Embeddings): pass @abstractmethod def update_by_paragraph_id(self, paragraph_id: str, instance: Dict): pass @abstractmethod def update_by_paragraph_ids(self, paragraph_ids: str, instance: Dict): pass @abstractmethod def update_by_source_id(self, source_id: str, instance: Dict): pass @abstractmethod def update_by_source_ids(self, source_ids: List[str], instance: Dict): pass @abstractmethod def delete_by_knowledge_id(self, knowledge_id: str): pass @abstractmethod def delete_by_document_id(self, document_id: str): pass @abstractmethod def delete_by_document_id_list(self, document_id_list: List[str]): pass @abstractmethod def delete_by_knowledge_id_list(self, knowledge_id_list: List[str]): pass @abstractmethod def delete_by_source_id(self, source_id: str, source_type: str): pass @abstractmethod def delete_by_source_ids(self, source_ids: List[str], source_type: str): pass @abstractmethod def delete_by_paragraph_id(self, paragraph_id: str): pass @abstractmethod def delete_by_paragraph_ids(self, paragraph_ids: List[str]): pass ================================================ FILE: apps/knowledge/vector/pg_vector.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: pg_vector.py @date:2023/10/19 15:28 @desc: """ import json import os from abc import ABC, abstractmethod from typing import Dict, List import uuid_utils.compat as uuid from django.contrib.postgres.search import SearchVector from django.db.models import QuerySet, Value from langchain_core.embeddings import Embeddings from common.db.search import generate_sql_by_query_dict from common.db.sql_execute import select_list from common.utils.common import get_file_content from common.utils.ts_vecto_util import to_ts_vector, to_query from knowledge.models import Embedding, SearchMode, SourceType from knowledge.vector.base_vector import BaseVectorStore, normalize_for_embedding from maxkb.conf import PROJECT_DIR class PGVector(BaseVectorStore): def delete_by_source_ids(self, source_ids: List[str], source_type: str): if len(source_ids) == 0: return QuerySet(Embedding).filter(source_id__in=source_ids, source_type=source_type).delete() def update_by_source_ids(self, source_ids: List[str], instance: Dict): QuerySet(Embedding).filter(source_id__in=source_ids).update(**instance) def vector_is_create(self) -> bool: # 项目启动默认是创建好的 不需要再创建 return True def vector_create(self): return True def _save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str, source_id: str, is_active: bool, embedding: Embeddings): text = normalize_for_embedding(text) text_embedding = [float(x) for x in embedding.embed_query(text)] embedding = Embedding( id=uuid.uuid7(), knowledge_id=knowledge_id, document_id=document_id, is_active=is_active, paragraph_id=paragraph_id, source_id=source_id, embedding=text_embedding, source_type=source_type, search_vector=to_ts_vector(text) ) embedding.save() return True def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_the_task_interrupted): texts = [normalize_for_embedding(row.get('text')) for row in text_list] embeddings = embedding.embed_documents(texts) embedding_list = [ Embedding( id=uuid.uuid7(), document_id=text_list[index].get('document_id'), paragraph_id=text_list[index].get('paragraph_id'), knowledge_id=text_list[index].get('knowledge_id'), is_active=text_list[index].get('is_active', True), source_id=text_list[index].get('source_id'), source_type=text_list[index].get('source_type'), embedding=[float(x) for x in embeddings[index]], search_vector=SearchVector(Value(to_ts_vector(text_list[index]['text']))) ) for index in range(0, len(texts))] if not is_the_task_interrupted(): QuerySet(Embedding).bulk_create(embedding_list) if len(embedding_list) > 0 else None return True def hit_test(self, query_text, knowledge_id_list: list[str], exclude_document_id_list: list[str], top_number: int, similarity: float, search_mode: SearchMode, embedding: Embeddings): if knowledge_id_list is None or len(knowledge_id_list) == 0: return [] exclude_dict = {} query_text = normalize_for_embedding(query_text) embedding_query = embedding.embed_query(query_text) query_set = QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list, is_active=True) if exclude_document_id_list is not None and len(exclude_document_id_list) > 0: exclude_dict.__setitem__('document_id__in', exclude_document_id_list) query_set = query_set.exclude(**exclude_dict) for search_handle in search_handle_list: if search_handle.support(search_mode): return search_handle.handle(query_set, query_text, embedding_query, top_number, similarity, search_mode) def query(self, query_text: str, query_embedding: List[float], knowledge_id_list: list[str], document_id_list: list[str], exclude_document_id_list: list[str], exclude_paragraph_list: list[str], is_active: bool, top_n: int, similarity: float, search_mode: SearchMode): exclude_dict = {} if knowledge_id_list is None or len(knowledge_id_list) == 0: return [] query_set = QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list, is_active=is_active) if document_id_list is not None and len(document_id_list) > 0: query_set = query_set.filter(document_id__in=document_id_list) if exclude_document_id_list is not None and len(exclude_document_id_list) > 0: query_set = query_set.exclude(document_id__in=exclude_document_id_list) if exclude_paragraph_list is not None and len(exclude_paragraph_list) > 0: query_set = query_set.exclude(paragraph_id__in=exclude_paragraph_list) query_set = query_set.exclude(**exclude_dict) for search_handle in search_handle_list: if search_handle.support(search_mode): return search_handle.handle(query_set, query_text, query_embedding, top_n, similarity, search_mode) def update_by_source_id(self, source_id: str, instance: Dict): QuerySet(Embedding).filter(source_id=source_id).update(**instance) def update_by_paragraph_id(self, paragraph_id: str, instance: Dict): QuerySet(Embedding).filter(paragraph_id=paragraph_id).update(**instance) def update_by_paragraph_ids(self, paragraph_id: str, instance: Dict): QuerySet(Embedding).filter(paragraph_id__in=paragraph_id).update(**instance) def delete_by_knowledge_id(self, knowledge_id: str): QuerySet(Embedding).filter(knowledge_id=knowledge_id).delete() def delete_by_knowledge_id_list(self, knowledge_id_list: List[str]): QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list).delete() def delete_by_document_id(self, document_id: str): QuerySet(Embedding).filter(document_id=document_id).delete() return True def delete_by_document_id_list(self, document_id_list: List[str]): if len(document_id_list) == 0: return True return QuerySet(Embedding).filter(document_id__in=document_id_list).delete() def delete_by_source_id(self, source_id: str, source_type: str): QuerySet(Embedding).filter(source_id=source_id, source_type=source_type).delete() return True def delete_by_paragraph_id(self, paragraph_id: str): QuerySet(Embedding).filter(paragraph_id=paragraph_id).delete() def delete_by_paragraph_ids(self, paragraph_ids: List[str]): QuerySet(Embedding).filter(paragraph_id__in=paragraph_ids).delete() class ISearch(ABC): @abstractmethod def support(self, search_mode: SearchMode): pass @abstractmethod def handle(self, query_set, query_text, query_embedding, top_number: int, similarity: float, search_mode: SearchMode): pass class EmbeddingSearch(ISearch): def handle(self, query_set, query_text, query_embedding, top_number: int, similarity: float, search_mode: SearchMode): exec_sql, exec_params = generate_sql_by_query_dict({'embedding_query': query_set}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'embedding_search.sql')), with_table_name=True) embedding_model = select_list(exec_sql, [ len(query_embedding), json.dumps(query_embedding), *exec_params, similarity, top_number ]) return embedding_model def support(self, search_mode: SearchMode): return search_mode.value == SearchMode.embedding.value class KeywordsSearch(ISearch): def handle(self, query_set, query_text, query_embedding, top_number: int, similarity: float, search_mode: SearchMode): exec_sql, exec_params = generate_sql_by_query_dict({'keywords_query': query_set}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'keywords_search.sql')), with_table_name=True) embedding_model = select_list(exec_sql, [ to_query(query_text), *exec_params, similarity, top_number ]) return embedding_model def support(self, search_mode: SearchMode): return search_mode.value == SearchMode.keywords.value class BlendSearch(ISearch): def handle(self, query_set, query_text, query_embedding, top_number: int, similarity: float, search_mode: SearchMode): exec_sql, exec_params = generate_sql_by_query_dict({'embedding_query': query_set}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "knowledge", 'sql', 'blend_search.sql')), with_table_name=True) embedding_model = select_list(exec_sql, [ len(query_embedding), json.dumps(query_embedding), to_query(query_text), *exec_params, similarity, top_number ]) return embedding_model def support(self, search_mode: SearchMode): return search_mode.value == SearchMode.blend.value search_handle_list = [EmbeddingSearch(), KeywordsSearch(), BlendSearch()] ================================================ FILE: apps/knowledge/views/__init__.py ================================================ from .document import * from .knowledge import * from .paragraph import * from .problem import * from .tag import * from .knowledge_workflow import * from .knowledge_workflow_version import * ================================================ FILE: apps/knowledge/views/common.py ================================================ from django.db.models import QuerySet from knowledge.models import Document def get_document_operation_object(document_id: str): document_model = QuerySet(model=Document).filter(id=document_id).first() if document_model is not None: return { "name": document_model.name, "type": document_model.type, } return {} def get_document_operation_object_batch(document_id_list: str): document_model_list = QuerySet(model=Document).filter(id__in=document_id_list) if document_model_list is not None: return { "name": f'[{",".join([document_model.name for document_model in document_model_list])}]', 'document_list': [{'name': document_model.name, 'type': document_model.type} for document_model in document_model_list] } return {} def get_knowledge_document_operation_object(knowledge_dict: dict, document_dict: dict): return { 'name': f'{knowledge_dict.get("name", "")}/{document_dict.get("name", "")}', 'dataset_name': knowledge_dict.get("name", ""), 'dataset_desc': knowledge_dict.get("desc", ""), 'dataset_type': knowledge_dict.get("type", ""), 'document_name': document_dict.get("name", ""), 'document_type': document_dict.get("type", ""), } ================================================ FILE: apps/knowledge/views/document.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from knowledge.api.document import DocumentSplitAPI, DocumentBatchAPI, DocumentBatchCreateAPI, DocumentCreateAPI, \ DocumentReadAPI, DocumentEditAPI, DocumentDeleteAPI, TableDocumentCreateAPI, QaDocumentCreateAPI, \ WebDocumentCreateAPI, CancelTaskAPI, BatchCancelTaskAPI, SyncWebAPI, RefreshAPI, BatchEditHitHandlingAPI, \ DocumentTreeReadAPI, DocumentSplitPatternAPI, BatchRefreshAPI, BatchGenerateRelatedAPI, TemplateExportAPI, \ DocumentExportAPI, DocumentMigrateAPI, DocumentDownloadSourceAPI, DocumentTagsAPI from knowledge.api.tag import DocsTagDeleteAPI from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.document import DocumentSerializers from knowledge.views.common import get_knowledge_document_operation_object, get_document_operation_object_batch, \ get_document_operation_object class DocumentView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create document'), summary=_('Create document'), operation_id=_('Create document'), # type: ignore request=DocumentCreateAPI.get_request(), parameters=DocumentCreateAPI.get_parameters(), responses=DocumentCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log(menu='document', operate="Create document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), {'name': r.data.get('name')}), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success( DocumentSerializers.Create( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}, ).save(request.data)) @extend_schema( methods=['GET'], description=_('Get document'), summary=_('Get document'), operation_id=_('Get document'), # type: ignore parameters=DocumentTreeReadAPI.get_parameters(), responses=DocumentTreeReadAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): raw_tags = request.query_params.getlist("tags[]") return result.success(DocumentSerializers.Query( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'tag': request.query_params.get('tag'), 'tag_exclude': request.query_params.get('tag_exclude'), 'tag_ids': [tag for tag in raw_tags if tag != 'NO_TAG'], 'no_tag': 'NO_TAG' in raw_tags, 'desc': request.query_params.get("desc"), 'user_id': request.query_params.get('user_id') } ).list()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( description=_('Get document details'), summary=_('Get document details'), operation_id=_('Get document details'), # type: ignore parameters=DocumentReadAPI.get_parameters(), responses=DocumentReadAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): operate = DocumentSerializers.Operate(data={ 'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id }) operate.is_valid(raise_exception=True) return result.success(operate.one()) @extend_schema( description=_('Modify document'), summary=_('Modify document'), operation_id=_('Modify document'), # type: ignore parameters=DocumentEditAPI.get_parameters(), request=DocumentEditAPI.get_request(), responses=DocumentEditAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Modify document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.Operate(data={ 'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id }).edit(request.data, with_valid=True)) @extend_schema( description=_('Delete document'), summary=_('Delete document'), operation_id=_('Delete document'), # type: ignore parameters=DocumentDeleteAPI.get_parameters(), responses=DocumentDeleteAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Delete document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def delete(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): operate = DocumentSerializers.Operate(data={ 'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id }) operate.is_valid(raise_exception=True) return result.success(operate.delete()) class Split(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['POST'], description=_('Segmented document'), summary=_('Segmented document'), operation_id=_('Segmented document'), # type: ignore parameters=DocumentSplitAPI.get_parameters(), request=DocumentSplitAPI.get_request(), responses=DocumentSplitAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): split_data = {'file': request.FILES.getlist('file')} request_data = request.data if 'patterns' in request.data and request.data.get('patterns') is not None and len( request.data.get('patterns')) > 0: split_data.__setitem__('patterns', request_data.getlist('patterns')) if 'limit' in request.data: split_data.__setitem__('limit', request_data.get('limit')) if 'with_filter' in request.data: split_data.__setitem__('with_filter', request_data.get('with_filter')) return result.success(DocumentSerializers.Split(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, }).parse(split_data)) class SplitPattern(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get a list of segment IDs'), description=_('Get a list of segment IDs'), operation_id=_('Get a list of segment IDs'), # type: ignore parameters=DocumentSplitPatternAPI.get_parameters(), responses=DocumentSplitPatternAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.SplitPattern( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).list()) class BatchEditHitHandling(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Modify document hit processing methods in batches'), description=_('Modify document hit processing methods in batches'), operation_id=_('Modify document hit processing methods in batches'), # type: ignore request=BatchEditHitHandlingAPI.get_request(), parameters=BatchEditHitHandlingAPI.get_parameters(), responses=BatchEditHitHandlingAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Modify document hit processing methods in batches", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list'))), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).batch_edit_hit_handling(request.data)) class SyncWeb(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Synchronize web site types'), summary=_('Synchronize web site types'), operation_id=_('Synchronize web site types'), # type: ignore parameters=SyncWebAPI.get_parameters(), request=SyncWebAPI.get_request(), responses=SyncWebAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Synchronize web site types", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.Sync( data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).sync()) class Refresh(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Refresh document vector library'), description=_('Refresh document vector library'), operation_id=_('Refresh document vector library'), # type: ignore parameters=RefreshAPI.get_parameters(), request=RefreshAPI.get_request(), responses=RefreshAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Refresh document vector library", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.Operate( data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).refresh(request.data.get('state_list'))) class CancelTask(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Cancel task'), description=_('Cancel task'), operation_id=_('Cancel task'), # type: ignore parameters=CancelTaskAPI.get_parameters(), request=CancelTaskAPI.get_request(), responses=CancelTaskAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Cancel task", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.Operate( data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).cancel(request.data)) class BatchCancelTask(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Cancel tasks in batches'), description=_('Cancel tasks in batches'), operation_id=_('Cancel tasks in batches'), # type: ignore parameters=BatchCancelTaskAPI.get_parameters(), request=BatchCancelTaskAPI.get_request(), responses=BatchCancelTaskAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Cancel tasks in batches", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch(data={ 'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).batch_cancel(request.data)) class BatchCreate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Create documents in batches'), summary=_('Create documents in batches'), operation_id=_('Create documents in batches'), # type: ignore request=DocumentBatchCreateAPI.get_request(), parameters=DocumentBatchCreateAPI.get_parameters(), responses=DocumentBatchCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Create documents in batches", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), {'name': f'[{",".join([document.get("name") for document in r.data])}]', 'document_list': r.data}), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).batch_save(request.data)) class BatchSync(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Batch sync documents'), summary=_('Batch sync documents'), operation_id=_('Batch sync documents'), # type: ignore request=DocumentBatchAPI.get_request(), parameters=DocumentBatchAPI.get_parameters(), responses=DocumentBatchAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Batch sync documents", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list'))), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).batch_sync(request.data)) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Delete documents in batches'), summary=_('Delete documents in batches'), operation_id=_('Delete documents in batches'), # type: ignore request=DocumentBatchAPI.get_request(), parameters=DocumentBatchAPI.get_parameters(), responses=DocumentBatchAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Delete documents in batches", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list'))), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).batch_delete(request.data)) class BatchRefresh(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Batch refresh document vector library'), operation_id=_('Batch refresh document vector library'), # type: ignore request=BatchRefreshAPI.get_request(), parameters=BatchRefreshAPI.get_parameters(), responses=BatchRefreshAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Batch refresh document vector library", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success( DocumentSerializers.Batch( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).batch_refresh(request.data)) class BatchAddTag(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], summary=_('Batch add tags to documents'), operation_id=_('Batch add tags to documents'), # type: ignore request=DocumentTagsAPI.get_request(), parameters=DocumentTagsAPI.get_parameters(), responses=DocumentTagsAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Batch add tags to documents", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('document_ids')) ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Batch( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).batch_add_tag(request.data)) class BatchGenerateRelated(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Batch generate related problems'), description=_('Batch generate related problems'), operation_id=_('Batch generate related problems'), # type: ignore request=BatchGenerateRelatedAPI.get_request(), parameters=BatchGenerateRelatedAPI.get_parameters(), responses=BatchGenerateRelatedAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Batch generate related problems", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('document_id_list')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.BatchGenerateRelated( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).batch_generate_related(request.data)) class BatchExport(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Export multiple document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return DocumentSerializers.Batch(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id }).batch_export({'id_list': request.data}) class BatchExportZip(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Export multiple document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return DocumentSerializers.Batch(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id }).batch_export_zip({'id_list': request.data}) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get document by pagination'), summary=_('Get document by pagination'), operation_id=_('Get document by pagination'), # type: ignore parameters=DocumentTreeReadAPI.get_parameters(), responses=DocumentTreeReadAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int): raw_tags = request.query_params.getlist("tags[]") return result.success(DocumentSerializers.Query( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'tag': request.query_params.get('tag'), 'tag_exclude': request.query_params.get('tag_exclude'), 'tag_ids': [tag for tag in raw_tags if tag != 'NO_TAG'], 'no_tag': 'NO_TAG' in raw_tags, 'desc': request.query_params.get("desc"), 'user_id': request.query_params.get('user_id'), 'status': request.query_params.get('status'), 'is_active': request.query_params.get('is_active'), 'hit_handling_method': request.query_params.get('hit_handling_method'), 'order_by': request.query_params.get('order_by'), } ).page(current_page, page_size)) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Export document'), operation_id=_('Export document'), # type: ignore parameters=DocumentExportAPI.get_parameters(), responses=DocumentExportAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Export document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return DocumentSerializers.Operate(data={ 'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id }).export() class ExportZip(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Export Zip document'), operation_id=_('Export Zip document'), # type: ignore parameters=DocumentExportAPI.get_parameters(), responses=DocumentExportAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Export Zip document", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return DocumentSerializers.Operate(data={ 'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id }).export_zip() class DownloadSourceFile(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Download source file'), operation_id=_('Download source file'), # type: ignore parameters=DocumentDownloadSourceAPI.get_parameters(), responses=DocumentDownloadSourceAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return DocumentSerializers.Operate(data={ 'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id }).download_source_file() class ReplaceSourceFile(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Replace source file'), operation_id=_('Replace source file'), # type: ignore parameters=DocumentDownloadSourceAPI.get_parameters(), responses=DocumentDownloadSourceAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_REPLACE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_REPLACE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.ReplaceSourceFile(data={ 'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id, 'file': request.FILES.get('file') }).replace()) class Tags(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get document tags'), description=_('Get document tags'), operation_id=_('Get document tags'), # type: ignore parameters=DocumentTagsAPI.get_parameters(), responses=DocumentTagsAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.Tags(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'name': request.query_params.get('name') }).list()) @extend_schema( summary=_('Add document tags'), description=_('Add document tags'), operation_id=_('Add document tags'), # type: ignore parameters=DocumentTagsAPI.get_parameters(), responses=DocumentTagsAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.AddTags(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'tag_ids': request.data }).add_tags()) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Delete document tags'), description=_('Delete document tags'), operation_id=_('Delete document tags'), # type: ignore parameters=DocumentTagsAPI.get_parameters(), request=DocumentTagsAPI.get_request(), responses=DocumentTagsAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Delete document tags", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(DocumentSerializers.DeleteTags(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'tag_ids': request.data }).delete_tags()) class BatchDeleteDocsTag(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_("Batch Delete Documents Tag"), description=_("Batch Delete Documents Tag"), parameters=DocsTagDeleteAPI.get_parameters(), request=DocsTagDeleteAPI.get_request(), responses=DocsTagDeleteAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Batch Delete Documents Tag", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data.get('id_list'))), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str): return result.success(DocumentSerializers.DeleteDocsTag(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id, }).batch_delete_docs_tag(request.data)) class Migrate(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Migrate documents in batches'), operation_id=_('Migrate documents in batches'), # type: ignore parameters=DocumentMigrateAPI.get_parameters(), request=DocumentMigrateAPI.get_request(), responses=DocumentMigrateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_MIGRATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_MIGRATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Migrate documents in batches", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object_batch(r.data) ), ) def put(self, request: Request, workspace_id, knowledge_id: str, target_knowledge_id: str): return result.success(DocumentSerializers.Migrate( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'target_knowledge_id': target_knowledge_id, 'document_id_list': request.data} ).migrate()) class WebDocumentView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create Web site documents'), summary=_('Create Web site documents'), operation_id=_('Create Web site documents'), # type: ignore request=WebDocumentCreateAPI.get_request(), parameters=WebDocumentCreateAPI.get_parameters(), responses=WebDocumentCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Create Web site documents", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), {'name': f'[{",".join([url for url in r.data.get("source_url_list", [])])}]', 'document_list': [{'name': url} for url in r.data.get("source_url_list", [])]}), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Create(data={ 'knowledge_id': knowledge_id, 'workspace_id': workspace_id }).save_web(request.data, with_valid=True)) class QaDocumentView(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( summary=_('Import QA and create documentation'), description=_('Import QA and create documentation'), operation_id=_('Import QA and create documentation'), # type: ignore request=QaDocumentCreateAPI.get_request(), parameters=QaDocumentCreateAPI.get_parameters(), responses=QaDocumentCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Import QA and create documentation", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), {'name': f'[{",".join([file.name for file in r.FILES.getlist("file")])}]', 'document_list': [{'name': file.name} for file in r.FILES.getlist("file")]}), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Create(data={ 'knowledge_id': knowledge_id, 'workspace_id': workspace_id }).save_qa({'file_list': request.FILES.getlist('file')}, with_valid=True)) class TableDocumentView(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( summary=_('Import tables and create documents'), description=_('Import tables and create documents'), operation_id=_('Import tables and create documents'), # type: ignore request=TableDocumentCreateAPI.get_request(), parameters=TableDocumentCreateAPI.get_parameters(), responses=TableDocumentCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate="Import tables and create documents", get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), {'name': f'[{",".join([file.name for file in r.FILES.getlist("file")])}]', 'document_list': [{'name': file.name} for file in r.FILES.getlist("file")]}), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(DocumentSerializers.Create( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).save_table({'file_list': request.FILES.getlist('file')}, with_valid=True)) class Template(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get QA template'), operation_id=_('Get QA template'), # type: ignore parameters=TemplateExportAPI.get_parameters(), responses=TemplateExportAPI.get_response(), tags=[_('Knowledge Base/Documentation')] # type: ignore ) def get(self, request: Request): return DocumentSerializers.Export(data={'type': request.query_params.get('type')}).export(with_valid=True) class TableTemplate(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get form template'), operation_id=_('Get form template'), # type: ignore parameters=TemplateExportAPI.get_parameters(), responses=TemplateExportAPI.get_response(), tags=[_('Knowledge Base/Documentation')]) # type: ignore def get(self, request: Request): return DocumentSerializers.Export(data={'type': request.query_params.get('type')}).table_export(with_valid=True) ================================================ FILE: apps/knowledge/views/knowledge.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from knowledge.api.knowledge import KnowledgeBaseCreateAPI, KnowledgeWebCreateAPI, KnowledgeTreeReadAPI, \ KnowledgeEditAPI, KnowledgeReadAPI, KnowledgePageAPI, SyncWebAPI, GenerateRelatedAPI, HitTestAPI, EmbeddingAPI, \ GetModelAPI, KnowledgeExportAPI from knowledge.models import KnowledgeScope from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.knowledge import KnowledgeSerializer from models_provider.serializers.model_serializer import ModelSerializer from tools.api.tool import GetInternalToolAPI class KnowledgeView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get knowledge by folder'), summary=_('Get knowledge by folder'), operation_id=_('Get knowledge by folder'), # type: ignore parameters=KnowledgeTreeReadAPI.get_parameters(), responses=KnowledgeTreeReadAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str): return result.success(KnowledgeSerializer.Query( data={ 'workspace_id': workspace_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'desc': request.query_params.get("desc"), 'scope': KnowledgeScope.WORKSPACE, 'user_id': request.user.id } ).list()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Edit knowledge'), summary=_('Edit knowledge'), operation_id=_('Edit knowledge'), # type: ignore parameters=KnowledgeEditAPI.get_parameters(), request=KnowledgeEditAPI.get_request(), responses=KnowledgeEditAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Modify knowledge base information", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).edit(request.data)) @extend_schema( methods=['DELETE'], description=_('Delete knowledge'), summary=_('Delete knowledge'), operation_id=_('Delete knowledge'), # type: ignore parameters=KnowledgeBaseCreateAPI.get_parameters(), request=KnowledgeBaseCreateAPI.get_request(), responses=KnowledgeBaseCreateAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Delete knowledge base", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def delete(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).delete()) @extend_schema( methods=['GET'], description=_('Get knowledge'), summary=_('Get knowledge'), operation_id=_('Get knowledge'), # type: ignore parameters=KnowledgeReadAPI.get_parameters(), responses=KnowledgeReadAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).one()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get the knowledge base paginated list'), summary=_('Get the knowledge base paginated list'), operation_id=_('Get the knowledge base paginated list'), # type: ignore parameters=KnowledgePageAPI.get_parameters(), responses=KnowledgePageAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str, current_page: int, page_size: int): return result.success(KnowledgeSerializer.Query( data={ 'workspace_id': workspace_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'desc': request.query_params.get("desc"), 'scope': KnowledgeScope.WORKSPACE, 'user_id': request.user.id, 'create_user': request.query_params.get('create_user'), } ).page(current_page, page_size)) class SyncWeb(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_("Synchronize the knowledge base of the website"), description=_("Synchronize the knowledge base of the website"), operation_id=_("Synchronize the knowledge base of the website"), # type: ignore parameters=SyncWebAPI.get_parameters(), request=SyncWebAPI.get_request(), responses=SyncWebAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_SYNC.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_SYNC.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Synchronize the knowledge base of the website", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.SyncWeb( data={ 'workspace_id': workspace_id, 'sync_type': request.query_params.get('sync_type'), 'knowledge_id': knowledge_id, 'user_id': str(request.user.id) } ).sync()) class HitTest(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], summary=_('Hit test list'), description=_('Hit test list'), operation_id=_('Hit test list'), # type: ignore parameters=HitTestAPI.get_parameters(), request=HitTestAPI.get_request(), responses=HitTestAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_HIT_TEST.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_HIT_TEST.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.HitTest( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_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 StoreKnowledge(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get Appstore tools"), summary=_("Get Appstore tools"), operation_id=_("Get Appstore tools"), # type: ignore responses=GetInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) def get(self, request: Request): return result.success(KnowledgeSerializer.StoreKnowledge(data={ 'user_id': request.user.id, 'name': request.query_params.get('name', ''), }).get_appstore_templates()) class Embedding(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Re-vectorize'), description=_('Re-vectorize'), operation_id=_('Re-vectorize'), # type: ignore parameters=EmbeddingAPI.get_parameters(), request=EmbeddingAPI.get_request(), responses=EmbeddingAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_VECTOR.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_VECTOR.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate='Re-vectorize', get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.Operate( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id, 'user_id': request.user.id} ).embedding()) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Export knowledge base'), operation_id=_('Export knowledge base'), # type: ignore parameters=KnowledgeExportAPI.get_parameters(), responses=KnowledgeExportAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Export knowledge base", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return KnowledgeSerializer.Operate(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id }).export_excel() class ExportZip(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Export knowledge base containing images'), operation_id=_('Export knowledge base containing images'), # type: ignore parameters=KnowledgeExportAPI.get_parameters(), responses=KnowledgeExportAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Export knowledge base containing images", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return KnowledgeSerializer.Operate(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id }).export_zip() class GenerateRelated(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Generate related'), description=_('Generate related'), operation_id=_('Generate related'), # type: ignore parameters=GenerateRelatedAPI.get_parameters(), request=GenerateRelatedAPI.get_request(), responses=GenerateRelatedAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_GENERATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_GENERATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='document', operate='Generate related documents', get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.Operate( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id, 'user_id': request.user.id} ).generate_related(request.data)) class Model(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], summary=_('Get model for knowledge base'), description=_('Get model for knowledge base'), operation_id=_('Get model for knowledge base'), # type: ignore parameters=GetModelAPI.get_parameters(), responses=GetModelAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str): return result.success(ModelSerializer.Query( data={ 'workspace_id': workspace_id, 'model_type': 'LLM' } ).list(workspace_id, True)) class EmbeddingModel(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str): return result.success(ModelSerializer.Query( data={ 'workspace_id': workspace_id, 'model_type': 'EMBEDDING' } ).list(workspace_id, True)) class TransformWorkflow(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Knowledge Base', operate="Modify knowledge base information", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeSerializer.TransformWorkflow( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).transform(request.data)) class Tags(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get all tags of knowledge base'), summary=_('Get all tags of knowledge base'), operation_id=_('Get all tags of knowledge base'), # type: ignore parameters=KnowledgeReadAPI.get_parameters(), responses=KnowledgeReadAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str): return result.success(KnowledgeSerializer.Tags(data={ 'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_ids': request.query_params.getlist('knowledge_ids[]') }).list()) class KnowledgeBaseView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create base knowledge'), summary=_('Create base knowledge'), operation_id=_('Create base knowledge'), # type: ignore parameters=KnowledgeBaseCreateAPI.get_parameters(), request=KnowledgeBaseCreateAPI.get_request(), responses=KnowledgeBaseCreateAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log( menu='knowledge Base', operate='Create base knowledge', get_operation_object=lambda r, k: {'name': r.data.get('name'), 'desc': r.data.get('desc')}, ) def post(self, request: Request, workspace_id: str): return result.success(KnowledgeSerializer.Create( data={'user_id': request.user.id, 'workspace_id': workspace_id} ).save_base(request.data)) class KnowledgeWebView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create web knowledge'), summary=_('Create web knowledge'), operation_id=_('Create web knowledge'), # type: ignore parameters=KnowledgeWebCreateAPI.get_parameters(), request=KnowledgeWebCreateAPI.get_request(), responses=KnowledgeWebCreateAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log( menu='Knowledge Base', operate="Create a web site knowledge base", get_operation_object=lambda r, k: {'name': r.data.get('name'), 'desc': r.data.get('desc'), 'first_list': r.FILES.getlist('file'), 'meta': {'source_url': r.data.get('source_url'), 'selector': r.data.get('selector'), 'embedding_model_id': r.data.get('embedding_model_id')}} , ) def post(self, request: Request, workspace_id: str): return result.success(KnowledgeSerializer.Create( data={'user_id': request.user.id, 'workspace_id': workspace_id} ).save_web(request.data)) ================================================ FILE: apps/knowledge/views/knowledge_workflow.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_api import SpeechToTextAPI from common.auth import TokenAuth from common.auth.authentication import has_permissions, get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result, DefaultResultSerializer from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi, KnowledgeWorkflowActionApi, \ KnowledgeWorkflowActionPageApi, KnowledgeWorkflowExportApi, KnowledgeWorkflowImportApi from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer, KnowledgeWorkflowActionSerializer, \ KnowledgeWorkflowMcpSerializer class KnowledgeDatasourceFormListView(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str): r = KnowledgeWorkflowSerializer.Datasource( data={'type': type, 'id': id, 'params': request.data, 'function_name': 'get_form_list'} ).action() return result.success(r) class KnowledgeDatasourceView(APIView): authentication_classes = [TokenAuth] @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str, function_name: str): return result.success(KnowledgeWorkflowSerializer.Datasource( data={'type': type, 'id': id, 'params': request.data, 'function_name': function_name}).action()) class KnowledgeWorkflowUploadDocumentView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Knowledge workflow upload document'), summary=_('Knowledge workflow upload document'), operation_id=_('Knowledge workflow upload document'), # type: ignore parameters=KnowledgeWorkflowActionApi.get_parameters(), request=KnowledgeWorkflowActionApi.get_request(), responses=KnowledgeWorkflowActionApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeWorkflowActionSerializer( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).upload_document(request.data, request.user, True)) class KnowledgeWorkflowActionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Knowledge workflow debug'), summary=_('Knowledge workflow debug'), operation_id=_('Knowledge workflow debug'), # type: ignore parameters=KnowledgeWorkflowActionApi.get_parameters(), request=KnowledgeWorkflowActionApi.get_request(), responses=KnowledgeWorkflowActionApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeWorkflowActionSerializer( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).action(request.data, request.user, True)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Page Knowledge workflow action'), summary=_('Page Knowledge workflow action'), operation_id=_('Page Knowledge workflow action'), # type: ignore parameters=KnowledgeWorkflowActionApi.get_parameters(), request=KnowledgeWorkflowActionPageApi.get_request(), responses=KnowledgeWorkflowActionApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int): return result.success( KnowledgeWorkflowActionSerializer(data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}) .page(current_page, page_size, request.query_params)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get knowledge workflow action'), summary=_('Get knowledge workflow action'), operation_id=_('Get knowledge workflow action'), # type: ignore parameters=KnowledgeWorkflowActionApi.get_parameters(), responses=KnowledgeWorkflowActionApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def get(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str): return result.success(KnowledgeWorkflowActionSerializer.Operate( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id}) .one()) class Cancel(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Cancel knowledge workflow action'), summary=_('Cancel knowledge workflow action'), operation_id=_('Cancel knowledge workflow action'), # type: ignore parameters=KnowledgeWorkflowActionApi.get_parameters(), responses=DefaultResultSerializer(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def post(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str): return result.success(KnowledgeWorkflowActionSerializer.Operate( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id}) .cancel()) class KnowledgeWorkflowView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create knowledge workflow'), summary=_('Create knowledge workflow'), operation_id=_('Create knowledge workflow'), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), responses=KnowledgeWorkflowApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def post(self, request: Request, workspace_id: str): return result.success(KnowledgeWorkflowSerializer.Create( data={'user_id': request.user.id, 'workspace_id': workspace_id} ).save_workflow(request.data)) class Publish(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Publishing an knowledge"), summary=_("Publishing an knowledge"), operation_id=_("Publishing an knowledge"), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), request=None, responses=DefaultResultSerializer, tags=[_('Knowledge')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Knowledge', operate='Publishing an knowledge', get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id'))) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success( KnowledgeWorkflowSerializer.Operate( data={'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).publish()) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Export knowledge workflow'), summary=_('Export knowledge workflow'), operation_id=_('Export knowledge workflow'), # type: ignore parameters=KnowledgeWorkflowExportApi.get_parameters(), request=None, responses=KnowledgeWorkflowExportApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ) ) @log(menu='Knowledge', operate="Export knowledge workflow", get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return KnowledgeWorkflowSerializer.Export( data={'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id} ).export() class Import(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Import knowledge workflow'), summary=_('Import knowledge workflow'), operation_id=_('Import knowledge workflow'), # type: ignore parameters=KnowledgeWorkflowImportApi.get_parameters(), request=KnowledgeWorkflowImportApi.get_request(), responses=KnowledgeWorkflowImportApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ) ) @log(menu='Knowledge', operate="Import knowledge workflow", get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), ) def post(self, request: Request, workspace_id: str, knowledge_id: str): is_import_tool = get_is_permissions(request, workspace_id=workspace_id)( PermissionConstants.TOOL_IMPORT.get_workspace_permission(), PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) return result.success(KnowledgeWorkflowSerializer.Import(data={ 'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id }).import_({'file': request.FILES.get('file')}, is_import_tool)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Edit knowledge workflow'), summary=_('Edit knowledge workflow'), operation_id=_('Edit knowledge workflow'), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), request=KnowledgeWorkflowApi.get_request(), responses=KnowledgeWorkflowApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ) ) @log( menu='Knowledge Base', operate="Modify knowledge workflow", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).edit(request.data)) @extend_schema( methods=['GET'], description=_('Get knowledge workflow'), summary=_('Get knowledge workflow'), operation_id=_('Get knowledge workflow'), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), responses=KnowledgeWorkflowApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).one()) class KnowledgeWorkflowVersionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get knowledge workflow version list'), summary=_('Get knowledge workflow version list'), operation_id=_('Get knowledge workflow version list'), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), responses=KnowledgeWorkflowApi.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND ), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(KnowledgeWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).one()) class McpServers(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("speech to text"), summary=_("speech to text"), operation_id=_("speech to text"), # type: ignore parameters=SpeechToTextAPI.get_parameters(), request=SpeechToTextAPI.get_request(), responses=SpeechToTextAPI.get_response(), tags=[_('Knowledge Base')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_application_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def post(self, request: Request, workspace_id, knowledge_id: str): return result.success(KnowledgeWorkflowMcpSerializer( data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id, 'user_id': request.user.id, 'knowledge_id': knowledge_id}).get_mcp_servers(request.data)) ================================================ FILE: apps/knowledge/views/knowledge_workflow_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py.py @date:2025/6/3 15:46 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from knowledge.api.knowledge_version import KnowledgeVersionListAPI, KnowledgeVersionPageAPI, \ KnowledgeVersionOperateAPI from knowledge.models import Knowledge from knowledge.serializers.knowledge_version import KnowledgeWorkflowVersionSerializer def get_knowledge_operation_object(knowledge_id): knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first() if knowledge_model is not None: return { 'name': knowledge_model.name } return {} class KnowledgeWorkflowVersionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the knowledge version list"), summary=_("Get the knowledge version list"), operation_id=_("Get the knowledge version list"), # type: ignore parameters=KnowledgeVersionListAPI.get_parameters(), responses=KnowledgeVersionListAPI.get_response(), tags=[_('Knowledge/Version')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id, knowledge_id: str): return result.success( KnowledgeWorkflowVersionSerializer.Query( data={'workspace_id': workspace_id}).list( {'name': request.query_params.get("name"), 'knowledge_id': knowledge_id})) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the list of knowledge versions by page"), summary=_("Get the list of knowledge versions by page"), operation_id=_("Get the list of knowledge versions by page"), # type: ignore parameters=KnowledgeVersionPageAPI.get_parameters(), responses=KnowledgeVersionPageAPI.get_response(), tags=[_('Knowledge/Version')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int): return result.success( KnowledgeWorkflowVersionSerializer.Query( data={'workspace_id': workspace_id}).page( {'name': request.query_params.get("name"), 'knowledge_id': knowledge_id}, current_page, page_size)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get knowledge version details"), summary=_("Get knowledge version details"), operation_id=_("Get knowledge version details"), # type: ignore parameters=KnowledgeVersionOperateAPI.get_parameters(), responses=KnowledgeVersionOperateAPI.get_response(), tags=[_('Knowledge/Version')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, knowledge_id: str, knowledge_version_id: str): return result.success( KnowledgeWorkflowVersionSerializer.Operate( data={'user_id': request.user, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'knowledge_version_id': knowledge_version_id}).one()) @extend_schema( methods=['PUT'], description=_("Modify knowledge version information"), summary=_("Modify knowledge version information"), operation_id=_("Modify knowledge version information"), # type: ignore parameters=KnowledgeVersionOperateAPI.get_parameters(), request=None, responses=KnowledgeVersionOperateAPI.get_response(), tags=[_('Knowledge/Version')] # type: ignore ) @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Knowledge', operate="Modify knowledge version information", get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, knowledge_version_id: str): return result.success( KnowledgeWorkflowVersionSerializer.Operate( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id, 'knowledge_version_id': knowledge_version_id, 'user_id': request.user.id}).edit( request.data)) ================================================ FILE: apps/knowledge/views/paragraph.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.views import APIView from rest_framework.views import Request from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from common.utils.common import query_params_to_single_dict from knowledge.api.paragraph import ParagraphReadAPI, ParagraphCreateAPI, ParagraphBatchDeleteAPI, ParagraphEditAPI, \ ParagraphGetAPI, ProblemCreateAPI, UnAssociationAPI, AssociationAPI, ParagraphPageAPI, \ ParagraphBatchGenerateRelatedAPI, ParagraphMigrateAPI, ParagraphAdjustOrderAPI from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.paragraph import ParagraphSerializers from knowledge.views import get_knowledge_document_operation_object, get_document_operation_object class ParagraphView(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Paragraph list'), description=_('Paragraph list'), operation_id=_('Paragraph list'), # type: ignore parameters=ParagraphReadAPI.get_parameters(), responses=ParagraphReadAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): q = ParagraphSerializers.Query( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id } ) return result.success(q.list()) @extend_schema( summary=_('Create Paragraph'), operation_id=_('Create Paragraph'), # type: ignore parameters=ParagraphCreateAPI.get_parameters(), request=ParagraphCreateAPI.get_request(), responses=ParagraphCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Create Paragraph', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.Create( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id} ).save(request.data)) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Batch Paragraph'), description=_('Batch Paragraph'), operation_id=_('Batch Paragraph'), # type: ignore parameters=ParagraphBatchDeleteAPI.get_parameters(), request=ParagraphBatchDeleteAPI.get_request(), responses=ParagraphBatchDeleteAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.Batch( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id} ).batch_delete(request.data)) class BatchMigrate(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Migrate paragraphs in batches'), operation_id=_('Migrate paragraphs in batches'), # type: ignore parameters=ParagraphMigrateAPI.get_parameters(), request=ParagraphMigrateAPI.get_request(), responses=ParagraphMigrateAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Migrate paragraphs in batches', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, target_knowledge_id: str, target_document_id): return result.success( ParagraphSerializers.Migrate(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'target_knowledge_id': target_knowledge_id, 'document_id': document_id, 'target_document_id': target_document_id, 'paragraph_id_list': request.data.get('id_list') }).migrate()) class BatchGenerateRelated(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Batch Generate Related'), description=_('Batch Generate Related'), operation_id=_('Batch Generate Related'), # type: ignore parameters=ParagraphBatchGenerateRelatedAPI.get_parameters(), request=ParagraphBatchGenerateRelatedAPI.get_request(), responses=ParagraphBatchGenerateRelatedAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Batch generate related', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.Batch( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id} ).batch_generate_related(request.data)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Modify paragraph data'), description=_('Modify paragraph data'), operation_id=_('Modify paragraph data'), # type: ignore parameters=ParagraphEditAPI.get_parameters(), request=ParagraphEditAPI.get_request(), responses=ParagraphEditAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Modify paragraph data', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str): o = ParagraphSerializers.Operate( data={ 'workspace_id': workspace_id, "paragraph_id": paragraph_id, 'knowledge_id': knowledge_id, 'document_id': document_id } ) o.is_valid(raise_exception=True) return result.success(o.edit(request.data)) @extend_schema( methods=['GET'], summary=_('Get paragraph details'), description=_('Get paragraph details'), operation_id=_('Get paragraph details'), # type: ignore parameters=ParagraphGetAPI.get_parameters(), responses=ParagraphGetAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str): o = ParagraphSerializers.Operate( data={ 'workspace_id': workspace_id, "paragraph_id": paragraph_id, 'knowledge_id': knowledge_id, 'document_id': document_id } ) o.is_valid(raise_exception=True) return result.success(o.one()) @extend_schema( methods=['DELETE'], summary=_('Delete paragraph'), description=_('Delete paragraph'), operation_id=_('Delete paragraph'), # type: ignore parameters=ParagraphGetAPI.get_parameters(), responses=ParagraphGetAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')]) # type: ignore @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Delete paragraph', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def delete(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str): o = ParagraphSerializers.Operate( data={ 'workspace_id': workspace_id, "paragraph_id": paragraph_id, 'knowledge_id': knowledge_id, 'document_id': document_id } ) o.is_valid(raise_exception=True) return result.success(o.delete()) class Problem(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], summary=_('Add associated questions'), description=_('Add associated questions'), operation_id=_('Add associated questions'), # type: ignore parameters=ProblemCreateAPI.get_parameters(), request=ProblemCreateAPI.get_request(), responses=ProblemCreateAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Add associated questions', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str): return result.success(ParagraphSerializers.Problem( data={ 'workspace_id': workspace_id, "knowledge_id": knowledge_id, 'document_id': document_id, 'paragraph_id': paragraph_id } ).save(request.data, with_valid=True)) @extend_schema( methods=['GET'], summary=_('Get a list of paragraph questions'), description=_('Get a list of paragraph questions'), operation_id=_('Get a list of paragraph questions'), # type: ignore parameters=ParagraphGetAPI.get_parameters(), responses=ParagraphGetAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str): return result.success(ParagraphSerializers.Problem( data={ 'workspace_id': workspace_id, "knowledge_id": knowledge_id, 'document_id': document_id, 'paragraph_id': paragraph_id } ).list(with_valid=True)) class UnAssociation(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Disassociation issue'), description=_('Disassociation issue'), operation_id=_('Disassociation issue'), # type: ignore parameters=UnAssociationAPI.get_parameters(), request=UnAssociationAPI.get_request(), responses=UnAssociationAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Disassociation issue', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ) ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.Association( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'paragraph_id': request.query_params.get('paragraph_id'), 'problem_id': request.query_params.get('problem_id') } ).un_association()) class Association(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Related questions'), description=_('Related questions'), operation_id=_('Related questions'), # type: ignore parameters=AssociationAPI.get_parameters(), request=AssociationAPI.get_request(), responses=AssociationAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='Paragraph', operate='Related questions', get_operation_object=lambda r, keywords: get_knowledge_document_operation_object( get_knowledge_operation_object(keywords.get('knowledge_id')), get_document_operation_object(keywords.get('document_id')) ), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.Association( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'paragraph_id': request.query_params.get('paragraph_id'), 'problem_id': request.query_params.get('problem_id') } ).association()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], summary=_('Get paragraph list by pagination'), description=_('Get paragraph list by pagination'), operation_id=_('Get paragraph list by pagination'), # type: ignore parameters=ParagraphPageAPI.get_parameters(), responses=ParagraphPageAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, current_page: int, page_size: int): d = ParagraphSerializers.Query( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id } ) return result.success(d.page(current_page, page_size)) class AdjustPosition(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Adjust paragraph position'), description=_('Adjust paragraph position'), operation_id=_('Adjust paragraph position'), # type: ignore parameters=ParagraphAdjustOrderAPI.get_parameters(), request=ParagraphAdjustOrderAPI.get_request(), responses=ParagraphAdjustOrderAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str): return result.success(ParagraphSerializers.AdjustPosition( data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id, 'paragraph_id': request.query_params.get('paragraph_id'), } ).adjust_position(request.query_params.get('new_position'))) ================================================ FILE: apps/knowledge/views/problem.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.views import APIView from rest_framework.views import Request from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from common.utils.common import query_params_to_single_dict from knowledge.api.problem import ProblemReadAPI, ProblemBatchCreateAPI, BatchAssociationAPI, BatchDeleteAPI, \ ProblemPageAPI, ProblemDeleteAPI, ProblemEditAPI, ProblemParagraphAPI from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.problem import ProblemSerializers class ProblemView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], summary=_('Question list'), description=_('Question list'), operation_id=_('Question list'), # type: ignore parameters=ProblemReadAPI.get_parameters(), responses=ProblemReadAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str): q = ProblemSerializers.Query( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id } ) q.is_valid(raise_exception=True) return result.success(q.list()) @extend_schema( methods=['POST'], summary=_('Create question'), description=_('Create question'), operation_id=_('Create question'), # type: ignore parameters=ProblemBatchCreateAPI.get_parameters(), responses=ProblemBatchCreateAPI.get_response(), request=ProblemBatchCreateAPI.get_request(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='problem', operate='Create question', get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) , ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(ProblemSerializers.Create( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id} ).batch(request.data)) class Paragraph(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get a list of associated paragraphs'), description=_('Get a list of associated paragraphs'), operation_id=_('Get a list of associated paragraphs'), # type: ignore parameters=ProblemParagraphAPI.get_parameters(), responses=ProblemParagraphAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str): return result.success(ProblemSerializers.Operate( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'problem_id': problem_id } ).list_paragraph()) class BatchAssociation(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Batch associated paragraphs'), description=_('Batch associated paragraphs'), operation_id=_('Batch associated paragraphs'), # type: ignore request=BatchAssociationAPI.get_request(), parameters=BatchAssociationAPI.get_parameters(), responses=BatchAssociationAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='problem', operate='Batch associated paragraphs', get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(ProblemSerializers.BatchOperate( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).association(request.data)) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], summary=_('Batch deletion issues'), description=_('Batch deletion issues'), operation_id=_('Batch deletion issues'), # type: ignore request=BatchDeleteAPI.get_request(), parameters=BatchDeleteAPI.get_parameters(), responses=BatchDeleteAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='problem', operate='Batch deletion issues', get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(ProblemSerializers.BatchOperate( data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id} ).delete(request.data)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['DELETE'], summary=_('Delete question'), description=_('Delete question'), operation_id=_('Delete question'), # type: ignore parameters=ProblemDeleteAPI.get_parameters(), responses=ProblemDeleteAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='problem', operate='Delete question', get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def delete(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str): return result.success(ProblemSerializers.Operate( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'problem_id': problem_id } ).delete()) @extend_schema( methods=['PUT'], summary=_('Modify question'), description=_('Modify question'), operation_id=_('Modify question'), # type: ignore parameters=ProblemEditAPI.get_parameters(), request=ProblemEditAPI.get_request(), responses=ProblemEditAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='problem', operate='Modify question', get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')), ) def put(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str): return result.success(ProblemSerializers.Operate( data={ **query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'problem_id': problem_id } ).edit(request.data)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_('Get the list of questions by page'), description=_('Get the list of questions by page'), operation_id=_('Get the list of questions by page'), # type: ignore parameters=ProblemPageAPI.get_parameters(), responses=ProblemPageAPI.get_response(), tags=[_('Knowledge Base/Documentation/Paragraph/Question')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page, page_size): d = ProblemSerializers.Query( data={ **query_params_to_single_dict(request.query_params), 'knowledge_id': knowledge_id, 'workspace_id': workspace_id } ) d.is_valid(raise_exception=True) return result.success(d.page(current_page, page_size)) ================================================ FILE: apps/knowledge/views/tag.py ================================================ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from knowledge.api.tag import TagCreateAPI, TagDeleteAPI, TagEditAPI from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.tag import TagSerializers class KnowledgeTagView(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_("Create Knowledge Tag"), description=_("Create a new knowledge tag"), parameters=TagCreateAPI.get_parameters(), request=TagCreateAPI.get_request(), responses=TagCreateAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_TAG_CREATE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_TAG_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Create a knowledge tag", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) ) def post(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(TagSerializers.Create( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tags': request.data} ).insert()) @extend_schema( summary=_("Get Knowledge Tag"), description=_("Get knowledge tag"), parameters=TagCreateAPI.get_parameters(), request=TagCreateAPI.get_request(), responses=TagCreateAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Create a knowledge tag", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) ) def get(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(TagSerializers.Query(data={ 'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'name': request.query_params.get('name') }).list()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_("Update Knowledge Tag"), description=_("Update a knowledge tag"), parameters=TagEditAPI.get_parameters(), request=TagEditAPI.get_request(), responses=TagEditAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_TAG_EDIT.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_TAG_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Update a knowledge tag", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) ) def put(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str): return result.success(TagSerializers.Operate( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id} ).edit(request.data)) class Delete(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_("Delete Knowledge Tag"), description=_("Delete a knowledge tag"), parameters=TagDeleteAPI.get_parameters(), request=TagDeleteAPI.get_request(), responses=TagDeleteAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Delete a knowledge tag", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) ) def delete(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str, delete_type: str): return result.success(TagSerializers.Operate( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id} ).delete(delete_type)) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( summary=_("Batch Delete Knowledge Tag"), description=_("Batch Delete a knowledge tag"), parameters=TagDeleteAPI.get_parameters(), request=TagDeleteAPI.get_request(), responses=TagDeleteAPI.get_response(), tags=[_('Knowledge Base/Tag')] # type: ignore ) @has_permissions( PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_knowledge_permission(), PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND), ) @log( menu='tag', operate="Batch Delete knowledge tag", get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')) ) def put(self, request: Request, workspace_id: str, knowledge_id: str): return result.success(TagSerializers.BatchDelete( data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_ids': request.data} ).batch_delete()) ================================================ FILE: apps/local_model/__init__.py ================================================ ================================================ FILE: apps/local_model/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/local_model/apps.py ================================================ from django.apps import AppConfig class LocalModelConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'local_model' ================================================ FILE: apps/local_model/migrations/__init__.py ================================================ ================================================ FILE: apps/local_model/models/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2023/9/25 15:04 @desc: """ from .model_management import * ================================================ FILE: apps/local_model/models/model_management.py ================================================ # coding=utf-8 import uuid_utils.compat as uuid from django.db import models from common.mixins.app_model_mixin import AppModelMixin from local_model.models.user import User class Status(models.TextChoices): """系统设置类型""" SUCCESS = "SUCCESS", '成功' ERROR = "ERROR", "失败" DOWNLOAD = "DOWNLOAD", '下载中' PAUSE_DOWNLOAD = "PAUSE_DOWNLOAD", '暂停下载' class Model(AppModelMixin): """ 模型数据 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") name = models.CharField(max_length=128, verbose_name="名称", db_index=True) status = models.CharField(max_length=20, verbose_name='设置类型', choices=Status.choices, default=Status.SUCCESS, db_index=True) model_type = models.CharField(max_length=128, verbose_name="模型类型", db_index=True) model_name = models.CharField(max_length=128, verbose_name="模型名称", db_index=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True) credential = models.CharField(max_length=102400, verbose_name="模型认证信息") meta = models.JSONField(verbose_name="模型元数据,用于存储下载,或者错误信息", default=dict) model_params_form = models.JSONField(verbose_name="模型参数配置", default=list) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) class Meta: db_table = "model" unique_together = ['name', 'workspace_id'] ================================================ FILE: apps/local_model/models/system_setting.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: system_management.py @date:2024/3/19 13:47 @desc: 邮箱管理 """ from django.db import models from common.mixins.app_model_mixin import AppModelMixin class SettingType(models.IntegerChoices): """系统设置类型""" EMAIL = 0, '邮箱' RSA = 1, "私钥秘钥" LOG = 2, "日志清理时间" class SystemSetting(AppModelMixin): """ 系统设置 """ type = models.IntegerField(primary_key=True, verbose_name='设置类型', choices=SettingType.choices, default=SettingType.EMAIL) meta = models.JSONField(verbose_name="配置数据", default=dict) class Meta: db_table = "system_setting" ================================================ FILE: apps/local_model/models/user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 10:20 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.utils.common import password_encrypt class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") source = models.CharField(max_length=10, verbose_name="来源", default="LOCAL", db_index=True) is_active = models.BooleanField(default=True, db_index=True) language = models.CharField(max_length=10, verbose_name="语言", null=True, default=None) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True, db_index=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True, db_index=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [] class Meta: db_table = "user" def set_password(self, row_password): self.password = password_encrypt(row_password) self._password = row_password ================================================ FILE: apps/local_model/serializers/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/local_model/serializers/model_apply_serializers.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: model_apply_serializers.py @date:2024/8/20 20:39 @desc: """ import json import threading import time from django.db import connection from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from langchain_core.documents import Document from rest_framework import serializers from local_model.models import Model from local_model.serializers.rsa_util import rsa_long_decrypt from models_provider.impl.local_model_provider.local_model_provider import LocalModelProvider from common.cache.mem_cache import MemCache _lock = threading.Lock() locks = {} class ModelManage: cache = MemCache('model', {}) up_clear_time = time.time() @staticmethod def _get_lock(_id): lock = locks.get(_id) if lock is None: with _lock: lock = locks.get(_id) if lock is None: lock = threading.Lock() locks[_id] = lock return lock @staticmethod def get_model(_id, get_model): model_instance = ModelManage.cache.get(_id) if model_instance is None: lock = ModelManage._get_lock(_id) with lock: model_instance = ModelManage.cache.get(_id) if model_instance is None: model_instance = get_model(_id) ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8) else: if model_instance.is_cache_model(): ModelManage.cache.touch(_id, timeout=60 * 60 * 8) else: model_instance = get_model(_id) ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8) ModelManage.clear_timeout_cache() return model_instance @staticmethod def clear_timeout_cache(): if time.time() - ModelManage.up_clear_time > 60 * 60: threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start() ModelManage.up_clear_time = time.time() @staticmethod def delete_key(_id): if ModelManage.cache.has_key(_id): ModelManage.cache.delete(_id) def get_local_model(model, **kwargs): return LocalModelProvider().get_model(model.model_type, model.model_name, json.loads( rsa_long_decrypt(model.credential)), model_id=model.id, streaming=True, **kwargs) def get_embedding_model(model_id): model = QuerySet(Model).filter(id=model_id).first() # 手动关闭数据库连接 connection.close() embedding_model = ModelManage.get_model(model_id, lambda _id: get_local_model(model, use_local=True)) return embedding_model class EmbedDocuments(serializers.Serializer): texts = serializers.ListField(required=True, child=serializers.CharField(required=True, label=_('vector text')), label=_('vector text list')), class EmbedQuery(serializers.Serializer): text = serializers.CharField(required=True, label=_('vector text')) class CompressDocument(serializers.Serializer): page_content = serializers.CharField(required=True, label=_('text')) metadata = serializers.DictField(required=False, label=_('metadata')) class CompressDocuments(serializers.Serializer): documents = CompressDocument(required=True, many=True) query = serializers.CharField(required=True, label=_('query')) class ValidateModelSerializers(serializers.Serializer): model_name = serializers.CharField(required=True, label=_('model_name')) model_type = serializers.CharField(required=True, label=_('model_type')) model_credential = serializers.DictField(required=True, label="credential") def validate_model(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) LocalModelProvider().is_valid_credential(self.data.get('model_type'), self.data.get('model_name'), self.data.get('model_credential'), model_params={}, raise_exception=True) class ModelApplySerializers(serializers.Serializer): model_id = serializers.UUIDField(required=True, label=_('model id')) def embed_documents(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EmbedDocuments(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return model.embed_documents(instance.getlist('texts')) def embed_query(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EmbedQuery(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return model.embed_query(instance.get('text')) def compress_documents(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) CompressDocuments(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return [{'page_content': d.page_content, 'metadata': d.metadata} for d in model.compress_documents( [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document in instance.get('documents')], instance.get('query'))] def unload(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ModelManage.delete_key(self.data.get('model_id')) return True ================================================ FILE: apps/local_model/serializers/rsa_util.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: rsa_util.py @date:2023/11/3 11:13 @desc: """ import base64 import threading from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher from Crypto.PublicKey import RSA from django.core import cache from django.db.models import QuerySet from common.constants.cache_version import Cache_Version from local_model.models.system_setting import SystemSetting, SettingType lock = threading.Lock() rsa_cache = cache.cache cache_key = "rsa_key" # 对密钥加密的密码 secret_code = "mac_kb_password" def generate(): """ 生成 私钥秘钥对 :return:{key:'公钥',value:'私钥'} """ # 生成一个 2048 位的密钥 key = RSA.generate(2048) # 获取私钥 encrypted_key = key.export_key(passphrase=secret_code, pkcs=8, protection="scryptAndAES128-CBC") return {'key': key.publickey().export_key(), 'value': encrypted_key} def get_key_pair(): rsa_value = rsa_cache.get(cache_key) if rsa_value is None: with lock: rsa_value = rsa_cache.get(cache_key) if rsa_value is not None: return rsa_value rsa_value = get_key_pair_by_sql() version, get_key = Cache_Version.SYSTEM.value rsa_cache.set(get_key(key='rsa_key'), rsa_value, timeout=None, version=version) return rsa_value def get_key_pair_by_sql(): system_setting = QuerySet(SystemSetting).filter(type=SettingType.RSA.value).first() if system_setting is None: kv = generate() system_setting = SystemSetting(type=SettingType.RSA.value, meta={'key': kv.get('key').decode(), 'value': kv.get('value').decode()}) system_setting.save() return system_setting.meta def encrypt(msg, public_key: str | None = None): """ 加密 :param msg: 加密数据 :param public_key: 公钥 :return: 加密后的数据 """ if public_key is None: public_key = get_key_pair().get('key') cipher = PKCS1_cipher.new(RSA.importKey(public_key)) encrypt_msg = cipher.encrypt(msg.encode("utf-8")) return base64.b64encode(encrypt_msg).decode() def decrypt(msg, pri_key: str | None = None): """ 解密 :param msg: 需要解密的数据 :param pri_key: 私钥 :return: 解密后数据 """ if pri_key is None: pri_key = get_key_pair().get('value') cipher = PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code)) decrypt_data = cipher.decrypt(base64.b64decode(msg), 0) return decrypt_data.decode("utf-8") def rsa_long_encrypt(message, public_key: str | None = None, length=200): """ 超长文本加密 :param message: 需要加密的字符串 :param public_key 公钥 :param length: 1024bit的证书用100, 2048bit的证书用 200 :return: 加密后的数据 """ # 读取公钥 if public_key is None: public_key = get_key_pair().get('key') cipher = PKCS1_cipher.new(RSA.importKey(extern_key=public_key, passphrase=secret_code)) # 处理:Plaintext is too long. 分段加密 if len(message) <= length: # 对编码的数据进行加密,并通过base64进行编码 result = base64.b64encode(cipher.encrypt(message.encode('utf-8'))) else: rsa_text = [] # 对编码后的数据进行切片,原因:加密长度不能过长 for i in range(0, len(message), length): cont = message[i:i + length] # 对切片后的数据进行加密,并新增到text后面 rsa_text.append(cipher.encrypt(cont.encode('utf-8'))) # 加密完进行拼接 cipher_text = b''.join(rsa_text) # base64进行编码 result = base64.b64encode(cipher_text) return result.decode() def rsa_long_decrypt(message, pri_key: str | None = None, length=256): """ 超长文本解密,默认不加密 :param message: 需要解密的数据 :param pri_key: 秘钥 :param length : 1024bit的证书用128,2048bit证书用256位 :return: 解密后的数据 """ if pri_key is None: pri_key = get_key_pair().get('value') cipher = PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code)) base64_de = base64.b64decode(message) res = [] for i in range(0, len(base64_de), length): res.append(cipher.decrypt(base64_de[i:i + length], 0)) return b"".join(res).decode() ================================================ FILE: apps/local_model/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/local_model/urls.py ================================================ import os from django.urls import path from . import views app_name = "local_model" # @formatter:off urlpatterns = [ path('model/validate', views.LocalModelApply.Validate.as_view()), path('model//embed_documents', views.LocalModelApply.EmbedDocuments.as_view()), path('model//embed_query', views.LocalModelApply.EmbedQuery.as_view()), path('model//compress_documents', views.LocalModelApply.CompressDocuments.as_view()), path('model//unload', views.LocalModelApply.Unload.as_view()), ] ================================================ FILE: apps/local_model/views/__init__.py ================================================ # coding=utf-8 from .model_apply import * ================================================ FILE: apps/local_model/views/model_apply.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: model_apply.py @date:2024/8/20 20:38 @desc: """ from urllib.request import Request from rest_framework.views import APIView from common.result import result from local_model.serializers.model_apply_serializers import ModelApplySerializers, ValidateModelSerializers class LocalModelApply(APIView): class EmbedDocuments(APIView): def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).embed_documents(request.data)) class EmbedQuery(APIView): def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).embed_query(request.data)) class CompressDocuments(APIView): def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data)) class Unload(APIView): def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data)) class Validate(APIView): def post(self, request: Request): return result.success(ValidateModelSerializers(data=request.data).validate_model()) ================================================ FILE: apps/locales/en_US/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-06-18 17:34+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: apps/application/api/application_api.py:21 #: apps/application/serializers/application.py:153 msgid "Workflow Objects" msgstr "" #: apps/application/api/application_api.py:52 #: apps/application/api/application_chat.py:104 #: apps/application/api/application_chat_record.py:74 #: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79 #: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110 #: apps/xpack/api/user_group.py:61 msgid "Current page" msgstr "" #: apps/application/api/application_api.py:59 #: apps/application/api/application_chat.py:111 #: apps/application/api/application_chat_record.py:81 #: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86 #: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117 #: apps/xpack/api/user_group.py:68 msgid "Page size" msgstr "" #: apps/application/api/application_api.py:66 #: apps/application/serializers/application.py:156 #: apps/application/serializers/application.py:195 #: apps/application/serializers/application.py:274 #: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139 #: apps/knowledge/serializers/knowledge.py:52 #: apps/knowledge/serializers/knowledge.py:59 #: apps/tools/serializers/tool.py:453 #: apps/xpack/serializers/dataset_lark_serializer.py:55 msgid "folder id" msgstr "" #: apps/application/api/application_api.py:73 #: apps/application/serializers/application.py:149 #: apps/application/serializers/application.py:275 #: apps/application/serializers/application.py:282 #: apps/application/serializers/application.py:368 msgid "Application Name" msgstr "Agent Name" #: apps/application/api/application_api.py:80 #: apps/application/serializers/application.py:152 #: apps/application/serializers/application.py:276 #: apps/application/serializers/application.py:283 #: apps/application/serializers/application.py:284 #: apps/application/serializers/application.py:370 msgid "Application Description" msgstr "Agent Description" #: apps/application/api/application_api.py:87 #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47 #: apps/application/serializers/application.py:99 #: apps/application/serializers/application.py:277 #: apps/application/serializers/application.py:295 #: apps/application/serializers/application.py:413 #: apps/application/serializers/application.py:540 #: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357 #: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52 #: apps/users/api/user.py:110 apps/users/api/user.py:126 #: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82 #: apps/workspace/serializers/workspace_serializers.py:270 #: apps/workspace/serializers/workspace_serializers.py:306 #: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92 #: apps/xpack/serializers/chat_user.py:301 msgid "User ID" msgstr "" #: apps/application/api/application_chat.py:70 #: apps/application/serializers/application_chat.py:56 msgid "Minimum number of likes" msgstr "" #: apps/application/api/application_chat.py:76 #: apps/application/serializers/application_chat.py:58 msgid "Minimum number of clicks" msgstr "" #: apps/application/api/application_chat.py:82 #: apps/application/flow/step_node/condition_node/i_condition_node.py:18 #: apps/application/serializers/application_chat.py:59 msgid "Comparator" msgstr "" #: apps/application/api/application_chat_record.py:46 #: apps/application/api/application_chat_record.py:115 #: apps/application/serializers/application_chat.py:47 #: apps/application/serializers/application_chat_record.py:76 msgid "Chat ID" msgstr "" #: apps/application/api/application_chat_record.py:53 #: apps/application/serializers/application_chat_record.py:77 msgid "Is it in order" msgstr "" #: apps/application/api/application_chat_record.py:122 msgid "Chat Record ID" msgstr "" #: apps/application/api/application_chat_record.py:129 #: apps/shared/api/shared_knowledge.py:235 #: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47 #: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111 msgid "Knowledge ID" msgstr "" #: apps/application/api/application_chat_record.py:136 #: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86 msgid "Document ID" msgstr "" #: apps/application/api/application_chat_record.py:148 msgid "Paragraph ID" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26 msgid "Model type error" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36 #: apps/common/field/common.py:24 apps/common/field/common.py:37 msgid "Message type error" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55 msgid "Conversation list" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13 #: apps/application/flow/step_node/question_node/i_question_node.py:18 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13 #: apps/application/serializers/application.py:101 #: apps/application/serializers/application.py:285 #: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76 #: apps/shared/api/shared_model.py:98 msgid "Model id" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29 msgid "Paragraph List" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_chat_record.py:41 #: apps/application/serializers/application_chat_record.py:140 #: apps/application/serializers/application_chat_record.py:179 #: apps/application/serializers/application_chat_record.py:247 #: apps/application/serializers/application_chat_record.py:312 #: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114 msgid "Conversation ID" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62 #: apps/application/flow/step_node/application_node/i_application_node.py:14 #: apps/application/serializers/application_chat.py:182 #: apps/chat/serializers/chat.py:40 msgid "User Questions" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65 msgid "Post-processor" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68 msgid "Completion Question" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70 msgid "Streaming Output" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71 #: apps/xpack/serializers/resource_chat_user.py:93 msgid "Chat user id" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73 msgid "Chat user Type" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47 msgid "No reference segment settings" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81 msgid "Model settings" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28 #: apps/application/flow/step_node/question_node/i_question_node.py:29 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20 msgid "Model parameter settings" msgstr "" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91 msgid "message type error" msgstr "" #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224 #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270 msgid "" "Sorry, the AI model is not configured. Please go to the application to set " "up the AI model first." msgstr "Sorry, the agent's AI model is not configured. Please go to the agent settings to set up the AI model first." #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24 #: apps/application/serializers/application_chat_record.py:172 msgid "question" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27 msgid "History Questions" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18 #: apps/application/flow/step_node/question_node/i_question_node.py:24 msgid "Number of multi-round conversations" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37 msgid "Maximum length of the knowledge base paragraph" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16 #: apps/application/flow/step_node/question_node/i_question_node.py:21 #: apps/application/serializers/application.py:79 #: apps/application/serializers/application.py:124 #: apps/knowledge/serializers/common.py:72 msgid "Prompt word" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41 msgid "System prompt words (role)" msgstr "" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44 msgid "Completion problem" msgstr "" #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32 #: apps/application/serializers/application.py:215 msgid "Question completion prompt" msgstr "" #: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20 #: apps/application/serializers/common.py:87 #, python-brace-format msgid "" "() contains the user's question. Answer the guessed user's question based on " "the context ({question}) Requirement: Output a complete question and put it " "in the tag" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27 msgid "System completes question text" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39 msgid "Dataset id list" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33 msgid "List of document ids to exclude" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36 msgid "List of exclusion vector ids" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24 #: apps/application/serializers/application.py:84 #: apps/application/serializers/application_chat.py:185 msgid "Reference segment number" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42 msgid "Similarity" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30 #: apps/application/serializers/application.py:91 #: apps/knowledge/serializers/knowledge.py:100 #: apps/knowledge/serializers/knowledge.py:643 msgid "The type only supports embedding|keywords|blend" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31 #: apps/application/serializers/application.py:92 msgid "Retrieval Mode" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31 #: apps/application/serializers/application.py:113 #: apps/application/serializers/application.py:657 #: apps/application/serializers/application.py:664 #: apps/application/serializers/application.py:671 #: apps/knowledge/serializers/document.py:643 #: apps/knowledge/serializers/knowledge.py:220 #: apps/models_provider/serializers/model_serializer.py:116 #: apps/models_provider/serializers/model_serializer.py:134 #: apps/models_provider/serializers/model_serializer.py:370 #: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32 #: apps/shared/serializers/shared_model.py:65 #: apps/shared/serializers/shared_model.py:82 msgid "Model does not exist" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33 #, python-brace-format msgid "No permission to use this model {model_name}" msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42 msgid "" "The vector model of the associated knowledge base is inconsistent and the " "segmentation cannot be recalled." msgstr "" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44 msgid "The knowledge base setting is wrong, please reset the knowledge base" msgstr "" #: apps/application/flow/common.py:206 #, python-brace-format msgid "The branch {branch} of the {node} node needs to be connected" msgstr "" #: apps/application/flow/common.py:212 #, python-brace-format msgid "{node} Nodes cannot be considered as end nodes" msgstr "" #: apps/application/flow/common.py:226 msgid "The starting node is required" msgstr "" #: apps/application/flow/common.py:228 msgid "There can only be one starting node" msgstr "" #: apps/application/flow/common.py:236 #, python-brace-format msgid "The node {node} model does not exist" msgstr "" #: apps/application/flow/common.py:246 #, python-brace-format msgid "Node {node} is unavailable" msgstr "" #: apps/application/flow/common.py:252 #, python-brace-format msgid "The library ID of node {node} cannot be empty" msgstr "" #: apps/application/flow/common.py:255 #, python-brace-format msgid "The function library for node {node} is not available" msgstr "" #: apps/application/flow/common.py:261 msgid "Basic information node is required" msgstr "" #: apps/application/flow/common.py:263 msgid "There can only be one basic information node" msgstr "" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15 #: apps/application/flow/step_node/question_node/i_question_node.py:20 #: apps/users/api/user.py:35 apps/users/api/user.py:102 msgid "Role Setting" msgstr "" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26 #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25 #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30 #: apps/application/flow/step_node/function_node/i_function_node.py:48 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23 #: apps/application/flow/step_node/question_node/i_question_node.py:27 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16 msgid "Whether to return content" msgstr "" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33 msgid "Context Type" msgstr "" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35 msgid "Whether to enable MCP" msgstr "" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36 msgid "MCP Server" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:12 #: apps/application/serializers/application.py:539 #: apps/application/serializers/application_access_token.py:44 #: apps/application/serializers/application_chat.py:38 #: apps/application/serializers/application_chat.py:54 #: apps/application/serializers/application_chat_record.py:43 #: apps/application/serializers/application_chat_record.py:75 #: apps/application/serializers/application_stats.py:35 #: apps/application/serializers/application_version.py:21 #: apps/application/serializers/application_version.py:67 #: apps/chat/serializers/chat.py:118 #: apps/chat/serializers/chat_authentication.py:80 #: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29 #: apps/xpack/api/platform.py:70 msgid "Application ID" msgstr "Agent ID" #: apps/application/flow/step_node/application_node/i_application_node.py:15 msgid "API Input Fields" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:17 msgid "User Input Fields" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:18 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25 #: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391 msgid "picture" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:19 #: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12 #: apps/chat/serializers/chat.py:58 msgid "document" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:20 #: apps/chat/serializers/chat.py:59 msgid "Audio" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:22 #: apps/chat/serializers/chat.py:62 msgid "Child Nodes" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:23 #: apps/application/flow/step_node/form_node/i_form_node.py:21 msgid "Form Data" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:57 msgid "" "Parameter value error: The uploaded document lacks file_id, and the document " "upload fails" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:66 msgid "" "Parameter value error: The uploaded image lacks file_id, and the image " "upload fails" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:76 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails." msgstr "" #: apps/application/flow/step_node/condition_node/i_condition_node.py:19 #: apps/models_provider/api/provide.py:24 msgid "value" msgstr "" #: apps/application/flow/step_node/condition_node/i_condition_node.py:20 msgid "Fields" msgstr "" #: apps/application/flow/step_node/condition_node/i_condition_node.py:24 msgid "Branch id" msgstr "" #: apps/application/flow/step_node/condition_node/i_condition_node.py:25 msgid "Branch Type" msgstr "" #: apps/application/flow/step_node/condition_node/i_condition_node.py:26 msgid "Condition or|and" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20 msgid "Response Type" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21 #: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13 msgid "Reference Field" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23 msgid "Direct answer content" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31 msgid "Reference field cannot be empty" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33 msgid "Reference field error" msgstr "" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36 msgid "Content cannot be empty" msgstr "" #: apps/application/flow/step_node/form_node/i_form_node.py:19 msgid "Form Configuration" msgstr "" #: apps/application/flow/step_node/form_node/i_form_node.py:20 msgid "Form output content" msgstr "" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22 #: apps/application/flow/step_node/function_node/i_function_node.py:24 msgid "Variable Name" msgstr "" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23 #: apps/application/flow/step_node/function_node/i_function_node.py:34 msgid "Variable Value" msgstr "" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27 msgid "Library ID" msgstr "" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36 msgid "The function has been deleted" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43 #, python-brace-format msgid "Field: {name} No value set" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59 #, python-brace-format msgid "Field: {name} Type: {_type} Value: {value} Unsupported types" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100 #, python-brace-format msgid "Field: {name} Type: {_type} Value: {value} Type error" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96 #: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259 msgid "type error" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106 #: apps/tools/serializers/tool.py:398 msgid "Function does not exist" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108 #, python-brace-format msgid "No permission to use this function {name}" msgstr "" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110 #, python-brace-format msgid "Function {name} is unavailable" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:25 msgid "Is this field required" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:26 #: apps/knowledge/serializers/document.py:203 #: apps/tools/serializers/tool.py:120 msgid "type" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:28 msgid "The field only supports string|int|dict|array|float" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:30 #: apps/folders/serializers/folder.py:106 #: apps/folders/serializers/folder.py:141 #: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124 msgid "source" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:32 #: apps/tools/serializers/tool.py:126 msgid "The field only supports custom|reference" msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:40 #, python-brace-format msgid "{field}, this field is required." msgstr "" #: apps/application/flow/step_node/function_node/i_function_node.py:46 msgid "function" msgstr "" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14 msgid "Prompt word (positive)" msgstr "" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16 msgid "Prompt word (negative)" msgstr "" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20 msgid "Conversation storage type" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13 msgid "Mcp servers" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16 msgid "Mcp server" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18 msgid "Mcp tool" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21 msgid "Tool parameters" msgstr "" #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33 msgid "Maximum number of words in a quoted segment" msgstr "" #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27 #: apps/knowledge/serializers/knowledge.py:97 #: apps/knowledge/serializers/knowledge.py:640 msgid "similarity" msgstr "" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18 msgid "The audio file cannot be empty" msgstr "" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails" msgstr "" #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18 msgid "Text content" msgstr "" #: apps/application/models/application_chat.py:79 #: apps/xpack/serializers/channel/chat_manage.py:94 #: apps/xpack/serializers/channel/chat_manage.py:152 msgid "" "Sorry, no relevant content was found. Please re-describe your problem or " "provide more information. " msgstr "" #: apps/application/serializers/application.py:78 msgid "No reference status" msgstr "" #: apps/application/serializers/application.py:86 msgid "Acquaintance" msgstr "" #: apps/application/serializers/application.py:88 msgid "Maximum number of quoted characters" msgstr "" #: apps/application/serializers/application.py:95 msgid "Segment settings not referenced" msgstr "" #: apps/application/serializers/application.py:104 #: apps/application/serializers/application_chat_record.py:176 #: apps/application/serializers/application_chat_record.py:252 #: apps/application/serializers/application_chat_record.py:317 msgid "Knowledge base id" msgstr "" #: apps/application/serializers/application.py:105 msgid "Knowledge Base List" msgstr "" #: apps/application/serializers/application.py:119 msgid "The knowledge base id does not exist" msgstr "" #: apps/application/serializers/application.py:126 msgid "Role prompts" msgstr "" #: apps/application/serializers/application.py:128 msgid "No citation segmentation prompt" msgstr "" #: apps/application/serializers/application.py:130 msgid "Thinking process switch" msgstr "" #: apps/application/serializers/application.py:134 msgid "The thinking process begins to mark" msgstr "" #: apps/application/serializers/application.py:138 msgid "End of thinking process marker" msgstr "" #: apps/application/serializers/application.py:155 #: apps/application/serializers/application.py:203 #: apps/application/serializers/application.py:378 msgid "Opening remarks" msgstr "" #: apps/application/serializers/application.py:191 msgid "application name" msgstr "Agent name" #: apps/application/serializers/application.py:194 msgid "application describe" msgstr "Agent description" #: apps/application/serializers/application.py:197 #: apps/application/serializers/application.py:372 #: apps/common/constants/permission_constants.py:226 #: apps/common/constants/permission_constants.py:259 #: apps/common/constants/permission_constants.py:264 #: apps/models_provider/views/model.py:63 #: apps/models_provider/views/model.py:95 #: apps/models_provider/views/model.py:113 #: apps/models_provider/views/model.py:130 #: apps/models_provider/views/model.py:145 #: apps/models_provider/views/model.py:160 #: apps/models_provider/views/model.py:173 #: apps/models_provider/views/model.py:194 #: apps/models_provider/views/model.py:210 #: apps/models_provider/views/model_apply.py:29 #: apps/models_provider/views/model_apply.py:41 #: apps/models_provider/views/model_apply.py:53 #: apps/models_provider/views/provide.py:25 #: apps/models_provider/views/provide.py:48 #: apps/models_provider/views/provide.py:62 #: apps/models_provider/views/provide.py:80 #: apps/models_provider/views/provide.py:97 msgid "Model" msgstr "" #: apps/application/serializers/application.py:201 #: apps/application/serializers/application.py:376 msgid "Historical chat records" msgstr "" #: apps/application/serializers/application.py:206 #: apps/application/serializers/application.py:380 msgid "Related Knowledge Base" msgstr "" #: apps/application/serializers/application.py:213 #: apps/application/serializers/application.py:390 msgid "Question completion" msgstr "" #: apps/application/serializers/application.py:217 msgid "Application Type" msgstr "Agent Type" #: apps/application/serializers/application.py:221 msgid "Application type only supports SIMPLE|WORK_FLOW" msgstr "Agent type only supports SIMPLE|WORK_FLOW" #: apps/application/serializers/application.py:226 #: apps/application/serializers/application.py:394 msgid "Model parameters" msgstr "" #: apps/application/serializers/application.py:228 #: apps/application/serializers/application.py:396 msgid "Voice playback enabled" msgstr "" #: apps/application/serializers/application.py:230 #: apps/application/serializers/application.py:398 msgid "Voice playback model ID" msgstr "" #: apps/application/serializers/application.py:232 #: apps/application/serializers/application.py:400 msgid "Voice playback type" msgstr "" #: apps/application/serializers/application.py:234 #: apps/application/serializers/application.py:402 msgid "Voice playback autoplay" msgstr "" #: apps/application/serializers/application.py:236 #: apps/application/serializers/application.py:404 msgid "Voice recognition enabled" msgstr "" #: apps/application/serializers/application.py:238 #: apps/application/serializers/application.py:406 msgid "Speech recognition model ID" msgstr "" #: apps/application/serializers/application.py:240 #: apps/application/serializers/application.py:408 msgid "Voice recognition automatic transmission" msgstr "" #: apps/application/serializers/application.py:281 msgid "Primary key id" msgstr "" #: apps/application/serializers/application.py:286 msgid "Application type" msgstr "Agent type" #: apps/application/serializers/application.py:287 #: apps/xpack/serializers/resource_chat_user.py:34 #: apps/xpack/serializers/resource_chat_user.py:110 #: apps/xpack/serializers/resource_chat_user_group.py:17 #: apps/xpack/serializers/resource_chat_user_group.py:85 msgid "Resource type" msgstr "" #: apps/application/serializers/application.py:288 msgid "Affiliation user" msgstr "" #: apps/application/serializers/application.py:289 msgid "Creation time" msgstr "" #: apps/application/serializers/application.py:290 msgid "Modification time" msgstr "" #: apps/application/serializers/application.py:294 #: apps/application/serializers/application_chat_record.py:42 #: apps/application/serializers/application_chat_record.py:323 #: apps/application/serializers/application_version.py:40 #: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147 #: apps/users/api/user.py:64 apps/users/api/user.py:170 #: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66 #: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103 #: apps/workspace/serializers/workspace_serializers.py:239 #: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40 #: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104 #: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63 msgid "Workspace ID" msgstr "" #: apps/application/serializers/application.py:363 #: apps/knowledge/serializers/document.py:157 #: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57 #: apps/tools/serializers/tool.py:356 msgid "file" msgstr "" #: apps/application/serializers/application.py:384 msgid "Dataset settings" msgstr "" #: apps/application/serializers/application.py:387 msgid "Model setup" msgstr "" #: apps/application/serializers/application.py:391 msgid "Icon" msgstr "" #: apps/application/serializers/application.py:412 #: apps/application/serializers/application_api_key.py:33 #: apps/application/serializers/application_api_key.py:64 #: apps/folders/serializers/folder.py:101 #: apps/folders/serializers/folder.py:140 #: apps/folders/serializers/folder.py:195 #: apps/knowledge/serializers/document.py:253 #: apps/knowledge/serializers/document.py:347 #: apps/knowledge/serializers/document.py:408 #: apps/knowledge/serializers/document.py:502 #: apps/knowledge/serializers/document.py:736 #: apps/knowledge/serializers/document.py:888 #: apps/knowledge/serializers/document.py:963 #: apps/knowledge/serializers/document.py:983 #: apps/knowledge/serializers/document.py:1166 #: apps/knowledge/serializers/knowledge.py:208 #: apps/knowledge/serializers/knowledge.py:448 #: apps/knowledge/serializers/knowledge.py:557 #: apps/knowledge/serializers/knowledge.py:635 #: apps/knowledge/serializers/paragraph.py:134 #: apps/knowledge/serializers/paragraph.py:346 #: apps/knowledge/serializers/paragraph.py:438 #: apps/knowledge/serializers/paragraph.py:558 #: apps/knowledge/serializers/problem.py:176 #: apps/knowledge/serializers/problem.py:204 #: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86 #: apps/models_provider/api/model.py:99 #: apps/models_provider/serializers/model_serializer.py:259 #: apps/models_provider/serializers/model_serializer.py:323 #: apps/models_provider/serializers/model_serializer.py:392 #: apps/shared/api/shared_knowledge.py:131 #: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60 #: apps/shared/api/shared_tool.py:147 #: apps/shared/serializers/shared_knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:109 #: apps/shared/serializers/shared_knowledge.py:157 #: apps/shared/serializers/shared_model.py:110 #: apps/shared/serializers/shared_tool.py:45 #: apps/shared/serializers/shared_tool.py:86 #: apps/system_manage/serializers/user_resource_permission.py:74 #: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210 #: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358 #: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425 #: apps/tools/serializers/tool.py:452 #: apps/xpack/serializers/dataset_lark_serializer.py:45 #: apps/xpack/serializers/resource_chat_user.py:33 #: apps/xpack/serializers/resource_chat_user.py:109 #: apps/xpack/serializers/resource_chat_user_group.py:16 #: apps/xpack/serializers/resource_chat_user_group.py:84 msgid "workspace id" msgstr "" #: apps/application/serializers/application.py:459 msgid "" "The community version supports up to 5 applications. If you need more " "applications, please contact us (https://fit2cloud.com/)." msgstr "" #: apps/application/serializers/application.py:471 #: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56 #: apps/common/handle/impl/text/zip_split_handle.py:69 #: apps/knowledge/serializers/document.py:864 #: apps/knowledge/serializers/document.py:871 #: apps/tools/serializers/tool.py:370 msgid "Unsupported file format" msgstr "" #: apps/application/serializers/application.py:545 msgid "Application id does not exist" msgstr "Agent id does not exist" #: apps/application/serializers/application.py:591 msgid "work_flow is a required field" msgstr "" #: apps/application/serializers/application.py:695 #, python-brace-format msgid "Unknown knowledge base id {dataset_id}, unable to associate" msgstr "" #: apps/application/serializers/application_access_token.py:24 msgid "Reset Token" msgstr "" #: apps/application/serializers/application_access_token.py:25 msgid "Is it enabled" msgstr "" #: apps/application/serializers/application_access_token.py:28 msgid "Number of visits" msgstr "" #: apps/application/serializers/application_access_token.py:30 msgid "Whether to enable whitelist" msgstr "" #: apps/application/serializers/application_access_token.py:32 #: apps/application/serializers/application_access_token.py:33 msgid "Whitelist" msgstr "" #: apps/application/serializers/application_access_token.py:35 msgid "Whether to display knowledge sources" msgstr "" #: apps/application/serializers/application_access_token.py:37 #: apps/users/serializers/user.py:665 #: apps/xpack/serializers/application_setting_serializer.py:37 msgid "language" msgstr "" #: apps/application/serializers/application_api_key.py:21 msgid "Availability" msgstr "" #: apps/application/serializers/application_api_key.py:24 msgid "Is cross-domain allowed" msgstr "" #: apps/application/serializers/application_api_key.py:28 msgid "Cross-domain address" msgstr "" #: apps/application/serializers/application_api_key.py:29 msgid "Cross-domain list" msgstr "" #: apps/application/serializers/application_api_key.py:34 #: apps/application/serializers/application_api_key.py:65 #: apps/knowledge/serializers/knowledge.py:72 #: apps/xpack/serializers/application_setting_serializer.py:77 #: apps/xpack/serializers/dataset_lark_serializer.py:295 msgid "application id" msgstr "Agent id" #: apps/application/serializers/application_api_key.py:41 #: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332 #: apps/xpack/serializers/application_setting_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:81 #: apps/xpack/serializers/platform_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:138 #: apps/xpack/serializers/platform_serializer.py:149 msgid "Application does not exist" msgstr "Agent does not exist" #: apps/application/serializers/application_api_key.py:66 msgid "ApiKeyId" msgstr "" #: apps/application/serializers/application_api_key.py:87 msgid "APIKey does not exist" msgstr "" #: apps/application/serializers/application_chat.py:33 msgid "chat id" msgstr "" #: apps/application/serializers/application_chat.py:34 #: apps/application/serializers/application_chat.py:51 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_version.py:23 msgid "summary" msgstr "" #: apps/application/serializers/application_chat.py:35 msgid "Chat User ID" msgstr "" #: apps/application/serializers/application_chat.py:36 msgid "Chat User Type" msgstr "" #: apps/application/serializers/application_chat.py:37 msgid "Is delete" msgstr "" #: apps/application/serializers/application_chat.py:39 #: apps/application/serializers/application_stats.py:25 msgid "Number of conversations" msgstr "" #: apps/application/serializers/application_chat.py:40 #: apps/application/serializers/application_stats.py:29 msgid "Number of Likes" msgstr "" #: apps/application/serializers/application_chat.py:41 #: apps/application/serializers/application_stats.py:31 msgid "Number of thumbs-downs" msgstr "" #: apps/application/serializers/application_chat.py:42 msgid "Number of tags" msgstr "" #: apps/application/serializers/application_chat.py:46 msgid "Chat ID List" msgstr "" #: apps/application/serializers/application_chat.py:52 #: apps/application/serializers/application_stats.py:36 #: apps/xpack/serializers/operate_log_serializer.py:55 msgid "Start time" msgstr "" #: apps/application/serializers/application_chat.py:53 #: apps/application/serializers/application_stats.py:37 #: apps/xpack/serializers/operate_log_serializer.py:56 msgid "End time" msgstr "" #: apps/application/serializers/application_chat.py:61 msgid "Only supports and|or" msgstr "" #: apps/application/serializers/application_chat.py:183 msgid "Problem after optimization" msgstr "" #: apps/application/serializers/application_chat.py:184 msgid "answer" msgstr "" #: apps/application/serializers/application_chat.py:184 msgid "User feedback" msgstr "" #: apps/application/serializers/application_chat.py:186 msgid "Section title + content" msgstr "" #: apps/application/serializers/application_chat.py:187 #: apps/application/views/application_chat_record.py:139 #: apps/application/views/application_chat_record.py:140 #: apps/application/views/application_chat_record.py:141 #: apps/common/constants/permission_constants.py:248 msgid "Annotation" msgstr "" #: apps/application/serializers/application_chat.py:187 msgid "Consuming tokens" msgstr "" #: apps/application/serializers/application_chat.py:188 msgid "Time consumed (s)" msgstr "" #: apps/application/serializers/application_chat.py:189 msgid "Question Time" msgstr "" #: apps/application/serializers/application_chat_record.py:44 #: apps/application/serializers/application_chat_record.py:143 #: apps/application/serializers/application_chat_record.py:250 #: apps/application/serializers/application_chat_record.py:315 #: apps/chat/serializers/chat.py:45 msgid "Conversation record id" msgstr "" #: apps/application/serializers/application_chat_record.py:51 msgid "Application authentication information does not exist" msgstr "Agent authentication information does not exist" #: apps/application/serializers/application_chat_record.py:53 msgid "Displaying knowledge sources is not enabled" msgstr "" #: apps/application/serializers/application_chat_record.py:70 #: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274 msgid "Conversation does not exist" msgstr "" #: apps/application/serializers/application_chat_record.py:152 #: apps/application/serializers/application_chat_record.py:279 #: apps/application/serializers/application_chat_record.py:336 #: apps/chat/serializers/chat.py:205 msgid "Conversation record does not exist" msgstr "" #: apps/application/serializers/application_chat_record.py:168 msgid "Section title" msgstr "" #: apps/application/serializers/application_chat_record.py:169 msgid "Paragraph content" msgstr "" #: apps/application/serializers/application_chat_record.py:177 #: apps/application/serializers/application_chat_record.py:254 #: apps/application/serializers/application_chat_record.py:319 msgid "Document id" msgstr "" #: apps/application/serializers/application_chat_record.py:184 #: apps/application/serializers/application_chat_record.py:260 #: apps/knowledge/serializers/paragraph.py:246 msgid "The document id is incorrect" msgstr "" #: apps/application/serializers/application_chat_record.py:203 msgid "Conversation records that do not exist" msgstr "" #: apps/application/serializers/application_chat_record.py:321 msgid "Paragraph id" msgstr "" #: apps/application/serializers/application_chat_record.py:340 #, python-brace-format msgid "" "The paragraph id is wrong. The current conversation record does not exist. " "[{paragraph_id}] paragraph id" msgstr "" #: apps/application/serializers/application_stats.py:26 msgid "Number of new users" msgstr "" #: apps/application/serializers/application_stats.py:27 msgid "Total number of users" msgstr "" #: apps/application/serializers/application_stats.py:28 msgid "date" msgstr "" #: apps/application/serializers/application_stats.py:30 msgid "Tokens consumption" msgstr "" #: apps/application/serializers/application_version.py:36 msgid "Version Name" msgstr "" #: apps/application/serializers/application_version.py:69 msgid "Workflow version id" msgstr "" #: apps/application/serializers/application_version.py:79 #: apps/application/serializers/application_version.py:94 msgid "Workflow version does not exist" msgstr "" #: apps/application/views/application.py:41 #: apps/application/views/application.py:42 #: apps/application/views/application.py:43 msgid "Create an application" msgstr "" #: apps/application/views/application.py:47 #: apps/application/views/application.py:65 #: apps/application/views/application.py:82 #: apps/application/views/application.py:103 #: apps/application/views/application.py:123 #: apps/application/views/application.py:145 #: apps/application/views/application.py:166 #: apps/application/views/application.py:187 #: apps/application/views/application.py:206 #: apps/application/views/application_access_token.py:32 #: apps/application/views/application_access_token.py:47 #: apps/application/views/application_chat.py:102 #: apps/application/views/application_chat.py:122 #: apps/application/views/application_stats.py:33 #: apps/common/constants/permission_constants.py:224 #: apps/common/constants/permission_constants.py:234 #: apps/xpack/views/application_setting.py:29 #: apps/xpack/views/application_setting.py:47 msgid "Application" msgstr "" #: apps/application/views/application.py:60 #: apps/application/views/application.py:61 #: apps/application/views/application.py:62 msgid "Get the application list" msgstr "Get the agent list" #: apps/application/views/application.py:77 #: apps/application/views/application.py:78 #: apps/application/views/application.py:79 msgid "Get the application list by page" msgstr "Get the agent list by page" #: apps/application/views/application.py:97 #: apps/application/views/application.py:98 #: apps/application/views/application.py:99 msgid "Import Application" msgstr "" #: apps/application/views/application.py:117 #: apps/application/views/application.py:118 #: apps/application/views/application.py:119 msgid "Export application" msgstr "" #: apps/application/views/application.py:140 #: apps/application/views/application.py:141 #: apps/application/views/application.py:142 msgid "Deleting application" msgstr "" #: apps/application/views/application.py:160 #: apps/application/views/application.py:161 #: apps/application/views/application.py:162 msgid "Modify the application" msgstr "" #: apps/application/views/application.py:181 #: apps/application/views/application.py:182 #: apps/application/views/application.py:183 msgid "Get application details" msgstr "Get agent details" #: apps/application/views/application.py:200 #: apps/application/views/application.py:201 #: apps/application/views/application.py:202 msgid "Publishing an application" msgstr "" #: apps/application/views/application_access_token.py:27 #: apps/application/views/application_access_token.py:28 #: apps/application/views/application_access_token.py:29 msgid "Modify application access restriction information" msgstr "Modify agent access restriction information" #: apps/application/views/application_access_token.py:43 #: apps/application/views/application_access_token.py:44 #: apps/application/views/application_access_token.py:45 msgid "Get application access restriction information" msgstr "Get agent access restriction information" #: apps/application/views/application_api_key.py:31 #: apps/application/views/application_api_key.py:32 #: apps/application/views/application_api_key.py:33 msgid "Create application ApiKey" msgstr "Create agent ApiKey" #: apps/application/views/application_api_key.py:37 #: apps/application/views/application_api_key.py:57 #: apps/application/views/application_api_key.py:77 #: apps/application/views/application_api_key.py:99 msgid "Application Api Key" msgstr "Agent Api Key" #: apps/application/views/application_api_key.py:52 msgid "GET application ApiKey List" msgstr "GET agent ApiKey List" #: apps/application/views/application_api_key.py:53 #: apps/application/views/application_api_key.py:54 msgid "Create application ApiKey List" msgstr "Create agent ApiKey List" #: apps/application/views/application_api_key.py:71 #: apps/application/views/application_api_key.py:72 #: apps/application/views/application_api_key.py:73 msgid "Modify application API_KEY" msgstr "Modify agent API_KEY" #: apps/application/views/application_api_key.py:93 #: apps/application/views/application_api_key.py:94 #: apps/application/views/application_api_key.py:95 msgid "Delete Application API_KEY" msgstr "Delete Agent API_KEY" #: apps/application/views/application_chat.py:35 #: apps/application/views/application_chat.py:36 #: apps/application/views/application_chat.py:37 msgid "Get the conversation list" msgstr "" #: apps/application/views/application_chat.py:41 #: apps/application/views/application_chat.py:61 #: apps/application/views/application_chat.py:82 #: apps/application/views/application_chat_record.py:37 #: apps/application/views/application_chat_record.py:58 #: apps/application/views/application_chat_record.py:82 #: apps/application/views/application_chat_record.py:106 #: apps/application/views/application_chat_record.py:125 #: apps/application/views/application_chat_record.py:145 #: apps/application/views/application_chat_record.py:171 msgid "Application/Conversation Log" msgstr "Agent/Conversation Log" #: apps/application/views/application_chat.py:55 #: apps/application/views/application_chat.py:56 #: apps/application/views/application_chat.py:57 msgid "Get the conversation list by page" msgstr "" #: apps/application/views/application_chat.py:76 #: apps/application/views/application_chat.py:77 #: apps/application/views/application_chat.py:78 msgid "Export conversation" msgstr "" #: apps/application/views/application_chat.py:97 #: apps/application/views/application_chat.py:98 #: apps/application/views/application_chat.py:99 msgid "Get a temporary session id based on the application id" msgstr "Get a temporary session id based on the agent id" #: apps/application/views/application_chat.py:116 #: apps/application/views/application_chat.py:117 #: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93 #: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95 msgid "dialogue" msgstr "" #: apps/application/views/application_chat_record.py:31 #: apps/application/views/application_chat_record.py:32 #: apps/application/views/application_chat_record.py:33 msgid "Get the conversation record list" msgstr "" #: apps/application/views/application_chat_record.py:52 #: apps/application/views/application_chat_record.py:53 #: apps/application/views/application_chat_record.py:54 msgid "Get the conversation record list by page" msgstr "" #: apps/application/views/application_chat_record.py:76 #: apps/application/views/application_chat_record.py:77 #: apps/application/views/application_chat_record.py:78 msgid "Get conversation record details" msgstr "" #: apps/application/views/application_chat_record.py:100 #: apps/application/views/application_chat_record.py:101 #: apps/application/views/application_chat_record.py:102 msgid "Add to Knowledge Base" msgstr "" #: apps/application/views/application_chat_record.py:119 #: apps/application/views/application_chat_record.py:120 #: apps/application/views/application_chat_record.py:121 msgid "Get the list of marked paragraphs" msgstr "" #: apps/application/views/application_chat_record.py:165 #: apps/application/views/application_chat_record.py:166 #: apps/application/views/application_chat_record.py:167 msgid "Delete a Annotation" msgstr "" #: apps/application/views/application_stats.py:28 #: apps/application/views/application_stats.py:29 #: apps/application/views/application_stats.py:30 msgid "Dialogue-related statistical trends" msgstr "" #: apps/application/views/application_version.py:30 #: apps/application/views/application_version.py:31 #: apps/application/views/application_version.py:32 msgid "Get the application version list" msgstr "Get the agent version list" #: apps/application/views/application_version.py:35 #: apps/application/views/application_version.py:55 #: apps/application/views/application_version.py:76 #: apps/application/views/application_version.py:94 msgid "Application/Version" msgstr "Agent/Version" #: apps/application/views/application_version.py:50 #: apps/application/views/application_version.py:51 #: apps/application/views/application_version.py:52 msgid "Get the list of application versions by page" msgstr "Get the list of agent versions by page" #: apps/application/views/application_version.py:71 #: apps/application/views/application_version.py:72 #: apps/application/views/application_version.py:73 msgid "Get application version details" msgstr "Get agent version details" #: apps/application/views/application_version.py:88 #: apps/application/views/application_version.py:89 #: apps/application/views/application_version.py:90 msgid "Modify application version information" msgstr "Modify agent version information" #: apps/chat/api/chat_authentication_api.py:38 #: apps/chat/serializers/chat_authentication.py:28 #: apps/chat/serializers/chat_authentication.py:54 #: apps/xpack/serializers/chat_auth.py:25 msgid "access_token" msgstr "" #: apps/chat/api/chat_embed_api.py:24 msgid "host" msgstr "" #: apps/chat/api/chat_embed_api.py:31 #: apps/chat/serializers/chat_embed_serializers.py:25 msgid "protocol" msgstr "" #: apps/chat/api/chat_embed_api.py:38 #: apps/chat/serializers/chat_embed_serializers.py:26 #: apps/users/serializers/login.py:36 msgid "token" msgstr "" #: apps/chat/serializers/chat.py:42 msgid "Is the answer in streaming mode" msgstr "" #: apps/chat/serializers/chat.py:43 msgid "Do you want to reply again" msgstr "" #: apps/chat/serializers/chat.py:48 msgid "Node id" msgstr "" #: apps/chat/serializers/chat.py:51 msgid "Runtime node id" msgstr "" #: apps/chat/serializers/chat.py:54 msgid "Node parameters" msgstr "" #: apps/chat/serializers/chat.py:56 msgid "Global variables" msgstr "" #: apps/chat/serializers/chat.py:60 #: apps/common/constants/permission_constants.py:222 #: apps/common/constants/permission_constants.py:228 msgid "Other" msgstr "" #: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320 msgid "Client id" msgstr "" #: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321 msgid "Client Type" msgstr "" #: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322 #: apps/common/constants/permission_constants.py:240 msgid "Debug" msgstr "" #: apps/chat/serializers/chat.py:146 msgid "The number of visits exceeds today's visits" msgstr "" #: apps/chat/serializers/chat.py:157 msgid "The current model is not available" msgstr "" #: apps/chat/serializers/chat.py:159 msgid "The model is downloading, please try again later" msgstr "" #: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357 msgid "The application has not been published. Please use it after publishing." msgstr "The agent has not been published. Please use it after publishing." #: apps/chat/serializers/chat_authentication.py:50 #: apps/xpack/serializers/chat_auth.py:53 msgid "Invalid access_token" msgstr "" #: apps/chat/serializers/chat_authentication.py:89 msgid "Illegal User" msgstr "" #: apps/chat/serializers/chat_embed_serializers.py:24 msgid "Host" msgstr "" #: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38 #: apps/chat/views/chat.py:39 msgid "Application Anonymous Certification" msgstr "Agent Anonymous Certification" #: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64 #: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99 #: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27 #: apps/xpack/views/chat_user_auth.py:419 msgid "Chat" msgstr "" #: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60 #: apps/chat/views/chat.py:61 msgid "Get application related information" msgstr "Get agent related information" #: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77 #: apps/chat/views/chat.py:78 msgid "Get application authentication information" msgstr "Get agent authentication information" #: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116 #: apps/chat/views/chat.py:117 msgid "Get the session id according to the application id" msgstr "Get the session id according to the agent id" #: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132 #: apps/chat/views/chat.py:133 apps/users/views/login.py:70 #: apps/users/views/login.py:71 apps/users/views/login.py:72 msgid "Get captcha" msgstr "" #: apps/chat/views/chat.py:134 #: apps/common/constants/permission_constants.py:210 #: apps/users/views/login.py:41 apps/users/views/login.py:58 #: apps/users/views/login.py:73 apps/users/views/user.py:63 #: apps/users/views/user.py:77 apps/users/views/user.py:91 #: apps/users/views/user.py:108 apps/users/views/user.py:123 #: apps/users/views/user.py:136 apps/users/views/user.py:150 #: apps/users/views/user.py:164 apps/users/views/user.py:180 #: apps/users/views/user.py:193 apps/users/views/user.py:206 #: apps/users/views/user.py:217 apps/users/views/user.py:235 #: apps/users/views/user.py:251 apps/users/views/user.py:269 #: apps/users/views/user.py:286 apps/users/views/user.py:303 #: apps/users/views/user.py:321 apps/users/views/user.py:338 #: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206 msgid "User Management" msgstr "" #: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23 #: apps/chat/views/chat_embed.py:24 msgid "Get embedded js" msgstr "" #: apps/common/auth/authenticate.py:80 msgid "Not logged in, please log in first" msgstr "" #: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89 #: apps/common/auth/authenticate.py:95 msgid "Authentication information is incorrect! illegal user" msgstr "" #: apps/common/auth/authentication.py:98 msgid "No permission to access" msgstr "" #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49 #: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43 #: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49 msgid "Authentication information is incorrect" msgstr "" #: apps/common/auth/handle/impl/user_token.py:265 msgid "Login expired" msgstr "" #: apps/common/constants/exception_code_constants.py:31 #: apps/users/serializers/login.py:53 #: apps/xpack/serializers/chat_user_serializer.py:123 msgid "The username or password is incorrect" msgstr "" #: apps/common/constants/exception_code_constants.py:32 msgid "Please log in first and bring the user Token" msgstr "" #: apps/common/constants/exception_code_constants.py:33 #: apps/users/serializers/user.py:630 msgid "Email sending failed" msgstr "" #: apps/common/constants/exception_code_constants.py:34 msgid "Email format error" msgstr "" #: apps/common/constants/exception_code_constants.py:35 msgid "The email has been registered, please log in directly" msgstr "" #: apps/common/constants/exception_code_constants.py:36 msgid "The email is not registered, please register first" msgstr "" #: apps/common/constants/exception_code_constants.py:38 msgid "The verification code is incorrect or the verification code has expired" msgstr "" #: apps/common/constants/exception_code_constants.py:39 msgid "The username has been registered, please log in directly" msgstr "" #: apps/common/constants/exception_code_constants.py:41 msgid "" "The username cannot be empty and must be between 6 and 20 characters long." msgstr "" #: apps/common/constants/exception_code_constants.py:43 msgid "Password and confirmation password are inconsistent" msgstr "" #: apps/common/constants/exception_code_constants.py:44 msgid "The nickname is already registered" msgstr "" #: apps/common/constants/permission_constants.py:209 msgid "System Setting" msgstr "" #: apps/common/constants/permission_constants.py:211 #: apps/common/constants/permission_constants.py:272 #: apps/role_setting/views/role_setting.py:44 #: apps/role_setting/views/role_setting.py:67 #: apps/role_setting/views/role_setting.py:84 #: apps/role_setting/views/role_setting.py:103 #: apps/role_setting/views/role_setting.py:125 #: apps/role_setting/views/role_setting.py:145 #: apps/role_setting/views/role_setting.py:167 #: apps/role_setting/views/role_setting.py:191 #: apps/role_setting/views/role_setting.py:210 msgid "Role" msgstr "" #: apps/common/constants/permission_constants.py:212 #: apps/common/constants/permission_constants.py:270 #: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49 #: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80 #: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119 #: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155 #: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188 #: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223 #: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250 msgid "Workspace" msgstr "" #: apps/common/constants/permission_constants.py:213 msgid "Resource Application" msgstr "" #: apps/common/constants/permission_constants.py:214 msgid "Resource Knowledge" msgstr "" #: apps/common/constants/permission_constants.py:215 msgid "Resource Tool" msgstr "" #: apps/common/constants/permission_constants.py:216 msgid "Resource Model" msgstr "" #: apps/common/constants/permission_constants.py:217 msgid "Resource Permission" msgstr "" #: apps/common/constants/permission_constants.py:218 #: apps/shared/views/shared_dataset_lark_views.py:30 #: apps/shared/views/shared_dataset_lark_views.py:50 #: apps/shared/views/shared_knowledge.py:33 #: apps/shared/views/shared_knowledge.py:53 #: apps/shared/views/shared_knowledge.py:76 #: apps/shared/views/shared_knowledge.py:91 #: apps/shared/views/shared_knowledge.py:106 #: apps/shared/views/shared_knowledge.py:125 #: apps/shared/views/shared_knowledge.py:151 #: apps/shared/views/shared_knowledge.py:178 #: apps/shared/views/shared_knowledge.py:196 #: apps/shared/views/shared_knowledge.py:214 #: apps/shared/views/shared_knowledge.py:235 #: apps/shared/views/shared_knowledge.py:256 #: apps/shared/views/shared_knowledge.py:276 #: apps/shared/views/shared_knowledge.py:297 #: apps/shared/views/shared_knowledge.py:312 #: apps/shared/views/shared_knowledge.py:331 #: apps/shared/views/shared_knowledge.py:354 #: apps/shared/views/shared_knowledge.py:386 #: apps/shared/views/shared_knowledge.py:407 msgid "Shared Knowledge" msgstr "" #: apps/common/constants/permission_constants.py:219 #: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58 #: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107 #: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138 #: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166 #: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202 #: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234 #: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270 msgid "Shared Model" msgstr "" #: apps/common/constants/permission_constants.py:220 #: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49 #: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83 #: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116 #: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157 #: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194 #: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239 #: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273 #: apps/shared/views/shared_tool.py:294 msgid "Shared Tool" msgstr "" #: apps/common/constants/permission_constants.py:221 msgid "Operation Log" msgstr "" #: apps/common/constants/permission_constants.py:223 msgid "System Management" msgstr "" #: apps/common/constants/permission_constants.py:225 #: apps/common/constants/permission_constants.py:235 #: apps/common/constants/permission_constants.py:260 #: apps/common/constants/permission_constants.py:265 msgid "Knowledge" msgstr "" #: apps/common/constants/permission_constants.py:227 #: apps/common/constants/permission_constants.py:258 #: apps/common/constants/permission_constants.py:263 #: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61 #: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104 #: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146 #: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201 #: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250 #: apps/tools/views/tool.py:274 msgid "Tool" msgstr "" #: apps/common/constants/permission_constants.py:229 msgid "Read" msgstr "" #: apps/common/constants/permission_constants.py:230 msgid "Edit" msgstr "" #: apps/common/constants/permission_constants.py:231 msgid "Create" msgstr "" #: apps/common/constants/permission_constants.py:232 msgid "Delete" msgstr "" #: apps/common/constants/permission_constants.py:233 msgid "Email Setting" msgstr "" #: apps/common/constants/permission_constants.py:236 #: apps/common/constants/permission_constants.py:261 #: apps/common/constants/permission_constants.py:266 msgid "Document" msgstr "" #: apps/common/constants/permission_constants.py:237 #: apps/common/constants/permission_constants.py:262 #: apps/common/constants/permission_constants.py:267 msgid "Problem" msgstr "" #: apps/common/constants/permission_constants.py:238 msgid "Import" msgstr "" #: apps/common/constants/permission_constants.py:239 msgid "Export" msgstr "" #: apps/common/constants/permission_constants.py:241 msgid "Sync" msgstr "" #: apps/common/constants/permission_constants.py:242 msgid "Generate" msgstr "" #: apps/common/constants/permission_constants.py:243 msgid "Add Member" msgstr "" #: apps/common/constants/permission_constants.py:244 msgid "Remove Member" msgstr "" #: apps/common/constants/permission_constants.py:245 msgid "Vector" msgstr "" #: apps/common/constants/permission_constants.py:246 msgid "Migrate" msgstr "" #: apps/common/constants/permission_constants.py:247 msgid "Relate" msgstr "" #: apps/common/constants/permission_constants.py:249 msgid "Clear Policy" msgstr "" #: apps/common/constants/permission_constants.py:250 msgid "Login Auth" msgstr "" #: apps/common/constants/permission_constants.py:251 msgid "Display Settings" msgstr "" #: apps/common/constants/permission_constants.py:252 #: apps/common/constants/permission_constants.py:720 #: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38 #: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71 msgid "System API Key" msgstr "" #: apps/common/constants/permission_constants.py:253 #: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42 msgid "Appearance Settings" msgstr "" #: apps/common/constants/permission_constants.py:254 #: apps/common/constants/permission_constants.py:269 #: apps/xpack/views/system_chat_user.py:339 #: apps/xpack/views/system_chat_user.py:362 msgid "Chat User" msgstr "" #: apps/common/constants/permission_constants.py:255 #: apps/common/constants/permission_constants.py:268 msgid "User Group" msgstr "" #: apps/common/constants/permission_constants.py:256 msgid "Chat User Auth" msgstr "" #: apps/common/constants/permission_constants.py:257 msgid "Overview" msgstr "" #: apps/common/constants/permission_constants.py:271 #: apps/common/constants/permission_constants.py:671 #: apps/common/constants/permission_constants.py:677 #: apps/common/constants/permission_constants.py:683 #: apps/common/constants/permission_constants.py:689 msgid "Dialogue log" msgstr "" #: apps/common/constants/permission_constants.py:641 msgid "Embed third party" msgstr "" #: apps/common/constants/permission_constants.py:647 msgid "Access restrictions" msgstr "" #: apps/common/constants/permission_constants.py:653 msgid "Display settings" msgstr "" #: apps/common/constants/permission_constants.py:659 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75 msgid "API Key" msgstr "" #: apps/common/constants/permission_constants.py:665 msgid "Public settings" msgstr "" #: apps/common/constants/permission_constants.py:704 msgid "About" msgstr "" #: apps/common/constants/permission_constants.py:709 #: apps/users/views/user.py:88 apps/users/views/user.py:89 #: apps/users/views/user.py:90 msgid "Switch Language" msgstr "" #: apps/common/constants/permission_constants.py:714 msgid "Change Password" msgstr "" #: apps/common/constants/permission_constants.py:734 msgid "Sync users" msgstr "" #: apps/common/constants/permission_constants.py:755 #: apps/common/constants/permission_constants.py:808 msgid "Set up user groups" msgstr "" #: apps/common/event/__init__.py:27 msgid "The download process was interrupted, please try again" msgstr "" #: apps/common/event/listener_manage.py:90 #, python-brace-format msgid "Query vector data: {paragraph_id_list} error {error} {traceback}" msgstr "" #: apps/common/event/listener_manage.py:95 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id_list}" msgstr "" #: apps/common/event/listener_manage.py:107 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id_list} error {error} {traceback}" msgstr "" #: apps/common/event/listener_manage.py:113 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id_list}" msgstr "" #: apps/common/event/listener_manage.py:122 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id}" msgstr "" #: apps/common/event/listener_manage.py:147 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id} error {error} {traceback}" msgstr "" #: apps/common/event/listener_manage.py:152 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id}" msgstr "" #: apps/common/event/listener_manage.py:268 #, python-brace-format msgid "Start--->Embedding document: {document_id}" msgstr "" #: apps/common/event/listener_manage.py:288 #, python-brace-format msgid "Vectorized document: {document_id} error {error} {traceback}" msgstr "" #: apps/common/event/listener_manage.py:293 #, python-brace-format msgid "End--->Embedding document: {document_id}" msgstr "" #: apps/common/event/listener_manage.py:304 #, python-brace-format msgid "Start--->Embedding knowledge: {knowledge_id}" msgstr "" #: apps/common/event/listener_manage.py:308 #, python-brace-format msgid "Start--->Embedding document: {document_list}" msgstr "" #: apps/common/event/listener_manage.py:312 #: apps/knowledge/task/embedding.py:116 #, python-brace-format msgid "Vectorized knowledge: {knowledge_id} error {error} {traceback}" msgstr "" #: apps/common/event/listener_manage.py:315 #, python-brace-format msgid "End--->Embedding knowledge: {knowledge_id}" msgstr "" #: apps/common/exception/handle_exception.py:32 #: apps/common/handle/handle_exception.py:33 msgid "Unknown exception" msgstr "" #: apps/common/field/common.py:48 msgid "not a function" msgstr "" #: apps/common/forms/base_field.py:64 #, python-brace-format msgid "The field {field_label} is required" msgstr "" #: apps/common/forms/slider_field.py:56 #, python-brace-format msgid "The {field_label} cannot be less than {min}" msgstr "" #: apps/common/forms/slider_field.py:62 #, python-brace-format msgid "The {field_label} cannot be greater than {max}" msgstr "" #: apps/common/handle/impl/text/pdf_split_handle.py:281 #, python-brace-format msgid "This document has no preface and is treated as ordinary text: {e}" msgstr "" #: apps/common/job/clean_chat_job.py:23 msgid "start clean chat log" msgstr "" #: apps/common/job/clean_chat_job.py:69 msgid "end clean chat log" msgstr "" #: apps/common/job/clean_debug_file_job.py:21 msgid "start clean debug file" msgstr "" #: apps/common/job/clean_debug_file_job.py:25 msgid "end clean debug file" msgstr "" #: apps/common/result/api.py:17 apps/common/result/api.py:27 msgid "response code" msgstr "" #: apps/common/result/api.py:18 apps/common/result/api.py:19 #: apps/common/result/api.py:28 apps/common/result/api.py:29 msgid "error prompt" msgstr "" #: apps/common/result/api.py:43 msgid "total number of data" msgstr "" #: apps/common/result/api.py:44 msgid "current page" msgstr "" #: apps/common/result/api.py:45 msgid "page size" msgstr "" #: apps/common/result/result.py:31 #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Success" msgstr "" #: apps/common/utils/common.py:91 msgid "Text-to-speech node, the text content must be of string type" msgstr "" #: apps/common/utils/common.py:93 msgid "Text-to-speech node, the text content cannot be empty" msgstr "" #: apps/common/utils/common.py:246 #, python-brace-format msgid "Limit {count} exceeded, please contact us (https://fit2cloud.com/)." msgstr "" #: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17 #: apps/folders/serializers/folder.py:98 msgid "folder name" msgstr "" #: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19 #: apps/folders/serializers/folder.py:99 msgid "folder description" msgstr "" #: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23 #: apps/folders/serializers/folder.py:102 msgid "parent id" msgstr "" #: apps/folders/serializers/folder.py:75 msgid "Folder depth cannot exceed 5 levels" msgstr "" #: apps/folders/serializers/folder.py:100 msgid "folder user id" msgstr "" #: apps/folders/serializers/folder.py:105 #: apps/knowledge/serializers/knowledge.py:112 #: apps/knowledge/serializers/knowledge.py:207 #: apps/knowledge/serializers/knowledge.py:447 #: apps/knowledge/serializers/knowledge.py:559 #: apps/knowledge/serializers/knowledge.py:637 #: apps/models_provider/serializers/model_serializer.py:108 #: apps/models_provider/serializers/model_serializer.py:212 #: apps/models_provider/serializers/model_serializer.py:252 #: apps/shared/serializers/shared_knowledge.py:107 #: apps/shared/serializers/shared_knowledge.py:156 #: apps/shared/serializers/shared_tool.py:84 #: apps/system_manage/serializers/user_resource_permission.py:75 #: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209 #: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664 #: apps/xpack/serializers/dataset_lark_serializer.py:46 #: apps/xpack/serializers/dataset_lark_serializer.py:285 #: apps/xpack/serializers/system_api_key.py:23 msgid "user id" msgstr "" #: apps/folders/serializers/folder.py:123 msgid "Folder name already exists" msgstr "" #: apps/folders/serializers/folder.py:150 #: apps/folders/serializers/folder.py:182 msgid "Folder does not exist" msgstr "" #: apps/folders/serializers/folder.py:184 msgid "Cannot delete root folder" msgstr "" #: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32 #: apps/folders/views/folder.py:33 msgid "Create folder" msgstr "" #: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63 #: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110 #: apps/folders/views/folder.py:129 msgid "Folder" msgstr "" #: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59 #: apps/folders/views/folder.py:60 msgid "Get folder tree" msgstr "" #: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81 #: apps/folders/views/folder.py:82 msgid "Update folder" msgstr "" #: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106 #: apps/folders/views/folder.py:107 msgid "Get folder" msgstr "" #: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125 #: apps/folders/views/folder.py:126 msgid "Delete folder" msgstr "" #: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52 #: apps/knowledge/serializers/problem.py:40 msgid "problem list" msgstr "" #: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53 #: apps/knowledge/serializers/problem.py:41 msgid "problem" msgstr "" #: apps/knowledge/serializers/common.py:32 #: apps/knowledge/serializers/knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:29 msgid "source url" msgstr "" #: apps/knowledge/serializers/common.py:33 #: apps/knowledge/serializers/document.py:152 msgid "selector" msgstr "" #: apps/knowledge/serializers/common.py:40 #, python-brace-format msgid "URL error, cannot parse [{source_url}]" msgstr "" #: apps/knowledge/serializers/common.py:48 #: apps/knowledge/serializers/document.py:78 #: apps/knowledge/serializers/document.py:170 #: apps/knowledge/serializers/document.py:186 msgid "id list" msgstr "" #: apps/knowledge/serializers/common.py:58 #, python-brace-format msgid "The following id does not exist: {error_id_list}" msgstr "" #: apps/knowledge/serializers/common.py:74 #: apps/knowledge/serializers/document.py:166 #: apps/knowledge/serializers/document.py:171 #: apps/knowledge/serializers/document.py:178 msgid "state list" msgstr "" #: apps/knowledge/serializers/common.py:117 #: apps/knowledge/serializers/common.py:141 msgid "The knowledge base is inconsistent with the vector model" msgstr "" #: apps/knowledge/serializers/common.py:119 #: apps/knowledge/serializers/common.py:143 msgid "Knowledge base setting error, please reset the knowledge base" msgstr "" #: apps/knowledge/serializers/document.py:79 #: apps/knowledge/serializers/document.py:97 #: apps/knowledge/serializers/document.py:353 msgid "task type" msgstr "" #: apps/knowledge/serializers/document.py:87 #: apps/knowledge/serializers/document.py:105 msgid "task type not support" msgstr "" #: apps/knowledge/serializers/document.py:91 #: apps/knowledge/serializers/document.py:110 #: apps/knowledge/serializers/document.py:350 msgid "document name" msgstr "" #: apps/knowledge/serializers/document.py:93 msgid "source file id" msgstr "" #: apps/knowledge/serializers/document.py:113 #: apps/knowledge/serializers/document.py:194 msgid "The type only supports optimization|directly_return" msgstr "" #: apps/knowledge/serializers/document.py:115 #: apps/knowledge/serializers/document.py:187 #: apps/knowledge/serializers/document.py:351 msgid "hit handling method" msgstr "" #: apps/knowledge/serializers/document.py:118 #: apps/knowledge/serializers/document.py:189 msgid "directly return similarity" msgstr "" #: apps/knowledge/serializers/document.py:120 #: apps/knowledge/serializers/document.py:352 msgid "document is active" msgstr "" #: apps/knowledge/serializers/document.py:139 #: apps/knowledge/serializers/document.py:156 #: apps/knowledge/serializers/document.py:161 msgid "file list" msgstr "" #: apps/knowledge/serializers/document.py:140 msgid "limit" msgstr "" #: apps/knowledge/serializers/document.py:143 #: apps/knowledge/serializers/document.py:144 msgid "patterns" msgstr "" #: apps/knowledge/serializers/document.py:146 msgid "Auto Clean" msgstr "" #: apps/knowledge/serializers/document.py:150 #: apps/knowledge/serializers/document.py:151 msgid "document url list" msgstr "" #: apps/knowledge/serializers/document.py:175 #: apps/knowledge/serializers/document.py:182 msgid "document id list" msgstr "" #: apps/knowledge/serializers/document.py:176 #: apps/knowledge/serializers/paragraph.py:58 #: apps/models_provider/api/model.py:105 #: apps/models_provider/serializers/model_apply_serializers.py:51 #: apps/models_provider/serializers/model_serializer.py:107 #: apps/models_provider/serializers/model_serializer.py:364 #: apps/shared/api/shared_model.py:61 #: apps/shared/serializers/shared_model.py:54 msgid "model id" msgstr "" #: apps/knowledge/serializers/document.py:177 #: apps/knowledge/serializers/paragraph.py:59 msgid "prompt" msgstr "" #: apps/knowledge/serializers/document.py:201 msgid "The template type only supports excel|csv" msgstr "" #: apps/knowledge/serializers/document.py:254 #: apps/knowledge/serializers/document.py:348 #: apps/knowledge/serializers/document.py:409 #: apps/knowledge/serializers/document.py:504 #: apps/knowledge/serializers/document.py:889 #: apps/knowledge/serializers/document.py:964 #: apps/knowledge/serializers/document.py:984 #: apps/knowledge/serializers/document.py:1167 #: apps/knowledge/serializers/knowledge.py:209 #: apps/knowledge/serializers/knowledge.py:558 #: apps/knowledge/serializers/paragraph.py:70 #: apps/knowledge/serializers/paragraph.py:138 #: apps/knowledge/serializers/paragraph.py:239 #: apps/knowledge/serializers/paragraph.py:321 #: apps/knowledge/serializers/paragraph.py:347 #: apps/knowledge/serializers/paragraph.py:398 #: apps/knowledge/serializers/paragraph.py:439 #: apps/knowledge/serializers/paragraph.py:559 #: apps/knowledge/serializers/problem.py:62 #: apps/knowledge/serializers/problem.py:126 #: apps/knowledge/serializers/problem.py:177 #: apps/knowledge/serializers/problem.py:205 #: apps/shared/api/shared_knowledge.py:196 #: apps/shared/api/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:158 #: apps/shared/serializers/shared_knowledge.py:205 #: apps/xpack/serializers/dataset_lark_serializer.py:104 #: apps/xpack/serializers/dataset_lark_serializer.py:263 #: apps/xpack/serializers/dataset_lark_serializer.py:284 msgid "knowledge id" msgstr "" #: apps/knowledge/serializers/document.py:255 #: apps/knowledge/serializers/paragraph.py:441 msgid "target knowledge id" msgstr "" #: apps/knowledge/serializers/document.py:256 msgid "document list" msgstr "" #: apps/knowledge/serializers/document.py:257 #: apps/knowledge/serializers/document.py:410 #: apps/knowledge/serializers/document.py:503 #: apps/knowledge/serializers/document.py:737 #: apps/knowledge/serializers/paragraph.py:61 #: apps/knowledge/serializers/paragraph.py:71 #: apps/knowledge/serializers/paragraph.py:140 #: apps/knowledge/serializers/paragraph.py:240 #: apps/knowledge/serializers/paragraph.py:322 #: apps/knowledge/serializers/paragraph.py:349 #: apps/knowledge/serializers/paragraph.py:399 #: apps/knowledge/serializers/paragraph.py:440 #: apps/knowledge/serializers/paragraph.py:560 #: apps/knowledge/serializers/problem.py:36 #: apps/knowledge/serializers/problem.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:160 msgid "document id" msgstr "" #: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25 #: apps/xpack/serializers/operate_log_serializer.py:60 #: apps/xpack/serializers/operate_log_serializer.py:174 msgid "status" msgstr "" #: apps/knowledge/serializers/document.py:355 msgid "order by" msgstr "" #: apps/knowledge/serializers/document.py:417 #: apps/knowledge/serializers/document.py:510 #: apps/xpack/serializers/dataset_lark_serializer.py:167 #: apps/xpack/serializers/dataset_lark_serializer.py:189 msgid "document id not exist" msgstr "" #: apps/knowledge/serializers/document.py:419 #: apps/knowledge/serializers/knowledge.py:570 msgid "Synchronization is only supported for web site types" msgstr "" #: apps/knowledge/serializers/document.py:661 msgid "The task is being executed, please do not send it repeatedly." msgstr "" #: apps/knowledge/serializers/document.py:674 msgid "Section title (optional)" msgstr "" #: apps/knowledge/serializers/document.py:675 msgid "" "Section content (required, question answer, no more than 4096 characters)" msgstr "" #: apps/knowledge/serializers/document.py:676 msgid "Question (optional, one per line in the cell)" msgstr "" #: apps/knowledge/serializers/document.py:742 msgid "knowledge id not exist" msgstr "" #: apps/knowledge/serializers/document.py:898 msgid "The maximum size of the uploaded file cannot exceed {}MB" msgstr "" #: apps/knowledge/serializers/document.py:976 msgid "space" msgstr "" #: apps/knowledge/serializers/document.py:977 msgid "semicolon" msgstr "" #: apps/knowledge/serializers/document.py:977 msgid "comma" msgstr "" #: apps/knowledge/serializers/document.py:978 msgid "period" msgstr "" #: apps/knowledge/serializers/document.py:978 msgid "enter" msgstr "" #: apps/knowledge/serializers/document.py:979 msgid "blank line" msgstr "" #: apps/knowledge/serializers/document.py:1140 msgid "Hit handling method is required" msgstr "" #: apps/knowledge/serializers/document.py:1142 msgid "The hit processing method must be directly_return|optimization" msgstr "" #: apps/knowledge/serializers/knowledge.py:51 #: apps/knowledge/serializers/knowledge.py:58 #: apps/knowledge/serializers/knowledge.py:67 #: apps/knowledge/serializers/knowledge.py:108 #: apps/shared/api/shared_knowledge.py:117 #: apps/shared/api/shared_knowledge.py:150 #: apps/shared/serializers/shared_knowledge.py:20 #: apps/shared/serializers/shared_knowledge.py:26 #: apps/shared/serializers/shared_knowledge.py:59 #: apps/shared/serializers/shared_knowledge.py:106 #: apps/xpack/serializers/dataset_lark_serializer.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:289 msgid "knowledge name" msgstr "" #: apps/knowledge/serializers/knowledge.py:53 #: apps/knowledge/serializers/knowledge.py:60 #: apps/knowledge/serializers/knowledge.py:68 #: apps/knowledge/serializers/knowledge.py:110 #: apps/shared/api/shared_knowledge.py:124 #: apps/shared/api/shared_knowledge.py:157 #: apps/shared/serializers/shared_knowledge.py:21 #: apps/shared/serializers/shared_knowledge.py:27 #: apps/shared/serializers/shared_knowledge.py:60 #: apps/shared/serializers/shared_knowledge.py:108 #: apps/xpack/serializers/dataset_lark_serializer.py:53 #: apps/xpack/serializers/dataset_lark_serializer.py:291 msgid "knowledge description" msgstr "" #: apps/knowledge/serializers/knowledge.py:54 #: apps/knowledge/serializers/knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:22 #: apps/shared/serializers/shared_knowledge.py:28 msgid "knowledge embedding" msgstr "" #: apps/knowledge/serializers/knowledge.py:63 #: apps/shared/serializers/shared_knowledge.py:30 msgid "knowledge selector" msgstr "" #: apps/knowledge/serializers/knowledge.py:73 #: apps/xpack/serializers/dataset_lark_serializer.py:296 msgid "application id list" msgstr "Agent ID list" #: apps/knowledge/serializers/knowledge.py:75 msgid "file size limit" msgstr "" #: apps/knowledge/serializers/knowledge.py:76 msgid "file count limit" msgstr "" #: apps/knowledge/serializers/knowledge.py:95 #: apps/knowledge/serializers/knowledge.py:638 msgid "query text" msgstr "" #: apps/knowledge/serializers/knowledge.py:96 #: apps/knowledge/serializers/knowledge.py:639 msgid "top number" msgstr "" #: apps/knowledge/serializers/knowledge.py:98 #: apps/knowledge/serializers/knowledge.py:641 msgid "search mode" msgstr "" #: apps/knowledge/serializers/knowledge.py:113 msgid "knowledge scope" msgstr "" #: apps/knowledge/serializers/knowledge.py:169 #: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464 msgid "Folder not found" msgstr "" #: apps/knowledge/serializers/knowledge.py:236 #: apps/knowledge/serializers/knowledge.py:265 msgid "Failed to send the vectorization task, please try again later!" msgstr "" #: apps/knowledge/serializers/knowledge.py:315 #: apps/knowledge/serializers/knowledge.py:471 #: apps/knowledge/serializers/knowledge.py:533 #: apps/xpack/serializers/dataset_lark_serializer.py:82 #: apps/xpack/serializers/dataset_lark_serializer.py:340 msgid "Knowledge base name duplicate!" msgstr "" #: apps/knowledge/serializers/knowledge.py:341 #: apps/xpack/serializers/dataset_lark_serializer.py:359 #, python-brace-format msgid "Unknown application id {knowledge_id}, cannot be associated" msgstr "Unknown Agent ID {knowledge_id}, cannot be associated" #: apps/knowledge/serializers/knowledge.py:449 #: apps/shared/serializers/shared_knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:110 #: apps/shared/serializers/shared_tool.py:46 #: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456 #: apps/xpack/serializers/dataset_lark_serializer.py:47 msgid "scope" msgstr "" #: apps/knowledge/serializers/knowledge.py:460 msgid "" "The community version supports up to 50 knowledge bases. If you need more " "knowledge bases, please contact us (https://fit2cloud.com/)." msgstr "" #: apps/knowledge/serializers/knowledge.py:560 msgid "sync type" msgstr "" #: apps/knowledge/serializers/knowledge.py:562 msgid "The synchronization type only supports:replace|complete" msgstr "" #: apps/knowledge/serializers/knowledge.py:568 #: apps/knowledge/serializers/knowledge.py:649 msgid "id does not exist" msgstr "" #: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76 msgid "id" msgstr "" #: apps/knowledge/serializers/paragraph.py:39 #: apps/knowledge/serializers/problem.py:27 #: apps/knowledge/serializers/problem.py:31 #: apps/knowledge/serializers/problem.py:206 msgid "content" msgstr "" #: apps/knowledge/serializers/paragraph.py:41 #: apps/knowledge/serializers/paragraph.py:48 #: apps/knowledge/serializers/paragraph.py:51 #: apps/knowledge/serializers/paragraph.py:65 #: apps/knowledge/serializers/paragraph.py:67 #: apps/knowledge/serializers/paragraph.py:323 msgid "section title" msgstr "" #: apps/knowledge/serializers/paragraph.py:44 #: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164 #: apps/xpack/serializers/system_api_key.py:11 msgid "Is active" msgstr "" #: apps/knowledge/serializers/paragraph.py:56 #: apps/knowledge/serializers/paragraph.py:443 msgid "paragraph id list" msgstr "" #: apps/knowledge/serializers/paragraph.py:57 #: apps/knowledge/serializers/paragraph.py:72 #: apps/knowledge/serializers/paragraph.py:136 #: apps/knowledge/serializers/paragraph.py:350 #: apps/knowledge/serializers/paragraph.py:444 #: apps/knowledge/serializers/paragraph.py:561 #: apps/knowledge/serializers/problem.py:35 #: apps/knowledge/serializers/problem.py:50 msgid "paragraph id" msgstr "" #: apps/knowledge/serializers/paragraph.py:77 #: apps/knowledge/serializers/paragraph.py:145 msgid "Paragraph id does not exist" msgstr "" #: apps/knowledge/serializers/paragraph.py:108 msgid "Already associated, please do not associate again" msgstr "" #: apps/knowledge/serializers/paragraph.py:181 msgid "Problem id does not exist" msgstr "" #: apps/knowledge/serializers/paragraph.py:348 #: apps/knowledge/serializers/problem.py:26 #: apps/knowledge/serializers/problem.py:46 #: apps/knowledge/serializers/problem.py:56 #: apps/knowledge/serializers/problem.py:127 msgid "problem id" msgstr "" #: apps/knowledge/serializers/paragraph.py:358 msgid "Paragraph does not exist" msgstr "" #: apps/knowledge/serializers/paragraph.py:360 msgid "Problem does not exist" msgstr "" #: apps/knowledge/serializers/paragraph.py:435 msgid "The task is being executed, please do not send it again." msgstr "" #: apps/knowledge/serializers/paragraph.py:442 msgid "target document id" msgstr "" #: apps/knowledge/serializers/paragraph.py:453 msgid "The document to be migrated is consistent with the target document" msgstr "" #: apps/knowledge/serializers/paragraph.py:455 #, python-brace-format msgid "The document id does not exist [{document_id}]" msgstr "" #: apps/knowledge/serializers/paragraph.py:459 #, python-brace-format msgid "The target document id does not exist [{document_id}]" msgstr "" #: apps/knowledge/serializers/paragraph.py:573 msgid "new_position must be an integer" msgstr "" #: apps/knowledge/serializers/problem.py:45 #: apps/knowledge/serializers/problem.py:55 msgid "problem id list" msgstr "" #: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74 #, python-brace-format msgid "Failed to obtain vector model: {error} {traceback}" msgstr "" #: apps/knowledge/task/embedding.py:103 #, python-brace-format msgid "Start--->Vectorized knowledge: {knowledge_id}" msgstr "" #: apps/knowledge/task/embedding.py:107 #, python-brace-format msgid "Knowledge documentation: {document_names}" msgstr "" #: apps/knowledge/task/embedding.py:120 #, python-brace-format msgid "End--->Vectorized knowledge: {knowledge_id}" msgstr "" #: apps/knowledge/task/generate.py:106 #, python-brace-format msgid "" "Generate issue based on document: {document_id} error {error}{traceback}" msgstr "" #: apps/knowledge/task/generate.py:110 #, python-brace-format msgid "End--->Generate problem: {document_id}" msgstr "" #: apps/knowledge/task/handler.py:121 #, python-brace-format msgid "Association problem failed {error}" msgstr "" #: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47 #, python-brace-format msgid "Start--->Start synchronization web knowledge base:{knowledge_id}" msgstr "" #: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51 #, python-brace-format msgid "End--->End synchronization web knowledge base:{knowledge_id}" msgstr "" #: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53 #, python-brace-format msgid "Synchronize web knowledge base:{knowledge_id} error{error}{traceback}" msgstr "" #: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29 #: apps/knowledge/views/document.py:30 msgid "Create document" msgstr "" #: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57 #: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104 #: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160 #: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209 #: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267 #: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323 #: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382 #: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441 #: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501 #: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553 #: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599 #: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664 #: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723 #: apps/knowledge/views/document.py:737 #: apps/xpack/views/dataset_lark_views.py:72 #: apps/xpack/views/dataset_lark_views.py:91 #: apps/xpack/views/dataset_lark_views.py:111 #: apps/xpack/views/dataset_lark_views.py:132 msgid "Knowledge Base/Documentation" msgstr "" #: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53 #: apps/knowledge/views/document.py:54 msgid "Get document" msgstr "" #: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80 #: apps/knowledge/views/document.py:81 msgid "Get document details" msgstr "" #: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99 #: apps/knowledge/views/document.py:100 msgid "Modify document" msgstr "" #: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124 #: apps/knowledge/views/document.py:125 msgid "Delete document" msgstr "" #: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155 #: apps/knowledge/views/document.py:156 msgid "Segmented document" msgstr "" #: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187 #: apps/knowledge/views/document.py:188 msgid "Get a list of segment IDs" msgstr "" #: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204 #: apps/knowledge/views/document.py:205 msgid "Modify document hit processing methods in batches" msgstr "" #: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233 #: apps/knowledge/views/document.py:234 msgid "Synchronize web site types" msgstr "" #: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262 #: apps/knowledge/views/document.py:263 msgid "Refresh document vector library" msgstr "" #: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290 #: apps/knowledge/views/document.py:291 msgid "Cancel task" msgstr "" #: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318 #: apps/knowledge/views/document.py:319 msgid "Cancel tasks in batches" msgstr "" #: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347 #: apps/knowledge/views/document.py:348 msgid "Create documents in batches" msgstr "" #: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377 #: apps/knowledge/views/document.py:378 msgid "Batch sync documents" msgstr "" #: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407 #: apps/knowledge/views/document.py:408 msgid "Delete documents in batches" msgstr "" #: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437 msgid "Batch refresh document vector library" msgstr "" #: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467 #: apps/knowledge/views/document.py:468 msgid "Batch generate related problems" msgstr "" #: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497 #: apps/knowledge/views/document.py:498 msgid "Get document by pagination" msgstr "" #: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524 msgid "Export document" msgstr "" #: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550 msgid "Export Zip document" msgstr "" #: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576 msgid "Download source file" msgstr "" #: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595 msgid "Migrate documents in batches" msgstr "" #: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628 #: apps/knowledge/views/document.py:629 #: apps/shared/views/shared_document.py:570 msgid "Create Web site documents" msgstr "" #: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659 #: apps/knowledge/views/document.py:660 msgid "Import QA and create documentation" msgstr "" #: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690 #: apps/knowledge/views/document.py:691 msgid "Import tables and create documents" msgstr "" #: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720 msgid "Get QA template" msgstr "" #: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734 msgid "Get form template" msgstr "" #: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26 #: apps/knowledge/views/knowledge.py:27 msgid "Get knowledge by folder" msgstr "" #: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59 #: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106 #: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156 #: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218 #: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266 #: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319 #: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369 #: apps/knowledge/views/knowledge.py:397 #: apps/xpack/views/dataset_lark_views.py:29 #: apps/xpack/views/dataset_lark_views.py:50 msgid "Knowledge Base" msgstr "" #: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54 #: apps/knowledge/views/knowledge.py:55 msgid "Edit knowledge" msgstr "" #: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78 #: apps/knowledge/views/knowledge.py:79 msgid "Delete knowledge" msgstr "" #: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102 #: apps/knowledge/views/knowledge.py:103 msgid "Get knowledge" msgstr "" #: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123 #: apps/knowledge/views/knowledge.py:124 msgid "Get the knowledge base paginated list" msgstr "" #: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151 #: apps/knowledge/views/knowledge.py:152 msgid "Synchronize the knowledge base of the website" msgstr "" #: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183 #: apps/knowledge/views/knowledge.py:184 msgid "Hit test list" msgstr "" #: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213 #: apps/knowledge/views/knowledge.py:214 msgid "Re-vectorize" msgstr "" #: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239 msgid "Export knowledge base" msgstr "" #: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263 msgid "Export knowledge base containing images" msgstr "" #: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288 #: apps/knowledge/views/knowledge.py:289 msgid "Generate related" msgstr "" #: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315 #: apps/knowledge/views/knowledge.py:316 msgid "Get model for knowledge base" msgstr "" #: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339 #: apps/knowledge/views/knowledge.py:340 msgid "Get embedding model for knowledge base" msgstr "" #: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364 #: apps/knowledge/views/knowledge.py:365 msgid "Create base knowledge" msgstr "" #: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392 #: apps/knowledge/views/knowledge.py:393 msgid "Create web knowledge" msgstr "" #: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25 #: apps/knowledge/views/paragraph.py:26 msgid "Paragraph list" msgstr "" #: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53 #: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102 #: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167 #: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224 #: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289 #: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351 #: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415 msgid "Knowledge Base/Documentation/Paragraph" msgstr "" #: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49 msgid "Create Paragraph" msgstr "" #: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77 #: apps/knowledge/views/paragraph.py:78 msgid "Batch Paragraph" msgstr "" #: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98 msgid "Migrate paragraphs in batches" msgstr "" #: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133 #: apps/knowledge/views/paragraph.py:134 msgid "Batch Generate Related" msgstr "" #: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162 #: apps/knowledge/views/paragraph.py:163 msgid "Modify paragraph data" msgstr "" #: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195 #: apps/knowledge/views/paragraph.py:196 msgid "Get paragraph details" msgstr "" #: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220 #: apps/knowledge/views/paragraph.py:221 msgid "Delete paragraph" msgstr "" #: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254 #: apps/knowledge/views/paragraph.py:255 msgid "Add associated questions" msgstr "" #: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285 #: apps/knowledge/views/paragraph.py:286 msgid "Get a list of paragraph questions" msgstr "" #: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311 #: apps/knowledge/views/paragraph.py:312 msgid "Disassociation issue" msgstr "" #: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346 #: apps/knowledge/views/paragraph.py:347 msgid "Related questions" msgstr "" #: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381 #: apps/knowledge/views/paragraph.py:382 msgid "Get paragraph list by pagination" msgstr "" #: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410 #: apps/knowledge/views/paragraph.py:411 #: apps/resource_manage/views/paragraph.py:364 #: apps/resource_manage/views/paragraph.py:365 #: apps/resource_manage/views/paragraph.py:366 #: apps/shared/views/shared_paragraph.py:365 #: apps/shared/views/shared_paragraph.py:366 #: apps/shared/views/shared_paragraph.py:367 msgid "Adjust paragraph position" msgstr "" #: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24 #: apps/knowledge/views/problem.py:25 msgid "Question list" msgstr "" #: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53 #: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104 #: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157 #: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216 msgid "Knowledge Base/Documentation/Paragraph/Question" msgstr "" #: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48 #: apps/knowledge/views/problem.py:49 msgid "Create question" msgstr "" #: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74 #: apps/knowledge/views/problem.py:75 msgid "Get a list of associated paragraphs" msgstr "" #: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99 #: apps/knowledge/views/problem.py:100 msgid "Batch associated paragraphs" msgstr "" #: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126 #: apps/knowledge/views/problem.py:127 msgid "Batch deletion issues" msgstr "" #: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153 #: apps/knowledge/views/problem.py:154 msgid "Delete question" msgstr "" #: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181 #: apps/knowledge/views/problem.py:182 msgid "Modify question" msgstr "" #: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212 #: apps/knowledge/views/problem.py:213 msgid "Get the list of questions by page" msgstr "" #: apps/maxkb/settings/base.py:101 msgid "Intelligent customer service platform" msgstr "" #: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17 #: apps/models_provider/api/provide.py:23 #: apps/models_provider/api/provide.py:28 #: apps/models_provider/api/provide.py:30 #: apps/models_provider/api/provide.py:82 #: apps/models_provider/serializers/model_serializer.py:40 #: apps/models_provider/serializers/model_serializer.py:215 #: apps/models_provider/serializers/model_serializer.py:253 #: apps/models_provider/serializers/model_serializer.py:318 #: apps/models_provider/serializers/model_serializer.py:393 #: apps/shared/api/shared_model.py:18 #: apps/shared/serializers/shared_model.py:111 msgid "model name" msgstr "" #: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29 #: apps/models_provider/api/provide.py:70 #: apps/models_provider/api/provide.py:98 #: apps/models_provider/serializers/model_serializer.py:42 #: apps/models_provider/serializers/model_serializer.py:217 #: apps/models_provider/serializers/model_serializer.py:255 #: apps/models_provider/serializers/model_serializer.py:319 #: apps/models_provider/serializers/model_serializer.py:394 #: apps/shared/api/shared_model.py:25 #: apps/shared/serializers/shared_model.py:112 msgid "model type" msgstr "" #: apps/models_provider/api/model.py:51 #: apps/models_provider/serializers/model_serializer.py:43 #: apps/models_provider/serializers/model_serializer.py:219 #: apps/models_provider/serializers/model_serializer.py:256 #: apps/models_provider/serializers/model_serializer.py:320 #: apps/models_provider/serializers/model_serializer.py:395 #: apps/shared/api/shared_model.py:32 #: apps/shared/serializers/shared_model.py:113 msgid "base model" msgstr "" #: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18 #: apps/models_provider/api/provide.py:38 #: apps/models_provider/api/provide.py:76 #: apps/models_provider/api/provide.py:104 #: apps/models_provider/api/provide.py:126 #: apps/models_provider/serializers/model_serializer.py:41 #: apps/models_provider/serializers/model_serializer.py:254 #: apps/models_provider/serializers/model_serializer.py:321 #: apps/models_provider/serializers/model_serializer.py:396 #: apps/shared/api/shared_model.py:39 #: apps/shared/serializers/shared_model.py:114 msgid "provider" msgstr "" #: apps/models_provider/api/model.py:65 #: apps/models_provider/serializers/model_serializer.py:322 #: apps/models_provider/serializers/model_serializer.py:397 #: apps/shared/api/shared_model.py:46 #: apps/shared/serializers/shared_model.py:115 msgid "create user" msgstr "" #: apps/models_provider/api/provide.py:19 #: apps/xpack/serializers/application_setting_serializer.py:41 #: apps/xpack/serializers/system_params.py:21 msgid "icon" msgstr "" #: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134 msgid "input type" msgstr "" #: apps/models_provider/api/provide.py:35 msgid "label" msgstr "" #: apps/models_provider/api/provide.py:36 msgid "text field" msgstr "" #: apps/models_provider/api/provide.py:37 msgid "value field" msgstr "" #: apps/models_provider/api/provide.py:39 msgid "method" msgstr "" #: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119 #: apps/tools/serializers/tool.py:133 msgid "required" msgstr "" #: apps/models_provider/api/provide.py:41 msgid "default value" msgstr "" #: apps/models_provider/api/provide.py:42 msgid "relation show field dict" msgstr "" #: apps/models_provider/api/provide.py:43 msgid "relation trigger field dict" msgstr "" #: apps/models_provider/api/provide.py:44 msgid "trigger type" msgstr "" #: apps/models_provider/api/provide.py:45 msgid "attrs" msgstr "" #: apps/models_provider/api/provide.py:46 msgid "props info" msgstr "" #: apps/models_provider/base_model_provider.py:60 msgid "Model type cannot be empty" msgstr "" #: apps/models_provider/base_model_provider.py:85 msgid "The current platform does not support downloading models" msgstr "" #: apps/models_provider/base_model_provider.py:143 msgid "LLM" msgstr "" #: apps/models_provider/base_model_provider.py:144 msgid "Embedding Model" msgstr "" #: apps/models_provider/base_model_provider.py:145 msgid "Speech2Text" msgstr "" #: apps/models_provider/base_model_provider.py:146 msgid "TTS" msgstr "" #: apps/models_provider/base_model_provider.py:147 msgid "Vision Model" msgstr "" #: apps/models_provider/base_model_provider.py:148 msgid "Image Generation" msgstr "" #: apps/models_provider/base_model_provider.py:149 msgid "Rerank" msgstr "" #: apps/models_provider/base_model_provider.py:223 msgid "The model does not support" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42 msgid "" "With the GTE-Rerank text sorting series model developed by Alibaba Tongyi " "Lab, developers can integrate high-quality text retrieval and sorting " "through the LlamaIndex framework." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45 msgid "" "Chinese (including various dialects such as Cantonese), English, Japanese, " "and Korean support free switching between multiple languages." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48 msgid "" "CosyVoice is based on a new generation of large generative speech models, " "which can predict emotions, intonation, rhythm, etc. based on context, and " "has better anthropomorphic effects." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51 msgid "" "Universal text vector is Tongyi Lab's multi-language text unified vector " "model based on the LLM base. It provides high-level vector services for " "multiple mainstream languages around the world and helps developers quickly " "convert text data into high-quality vector data." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69 msgid "" "Tongyi Wanxiang - a large image model for text generation, supports " "bilingual input in Chinese and English, and supports the input of reference " "pictures for reference content or reference style migration. Key styles " "include but are not limited to watercolor, oil painting, Chinese painting, " "sketch, flat illustration, two-dimensional, and 3D. Cartoon." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95 msgid "Alibaba Cloud Bailian" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/azure_model_provider/credential/image.py:40 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:69 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/model/stt.py:43 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/openai_model_provider/credential/image.py:35 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:59 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:37 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51 #: apps/models_provider/impl/tencent_model_provider/model/tti.py:54 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57 #: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:76 #: apps/models_provider/impl/xf_model_provider/model/tts.py:101 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:32 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34 #: apps/models_provider/impl/xinference_model_provider/model/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56 #: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49 msgid "Hello" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/azure_model_provider/credential/image.py:30 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:59 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:23 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:58 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:41 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:19 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/openai_model_provider/credential/image.py:25 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:48 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:22 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:61 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:40 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:28 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:22 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/xf_model_provider/credential/image.py:29 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:66 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:24 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:22 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40 #, python-brace-format msgid "{model_type} Model type is not supported" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98 #, python-brace-format msgid "{key} is required" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61 #: apps/models_provider/impl/azure_model_provider/credential/image.py:50 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:40 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:77 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:58 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:42 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:45 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/openai_model_provider/credential/image.py:45 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:67 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:39 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:80 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:42 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/image.py:50 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:84 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:41 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:65 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59 #, python-brace-format msgid "" "Verification failed, please check whether the parameters are correct: {error}" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:23 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:23 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22 msgid "Temperature" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:24 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:24 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:42 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23 msgid "" "Higher values make the output more random, while lower values make it more " "focused and deterministic" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:32 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:43 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31 msgid "Output the maximum Tokens" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31 msgid "Specify the maximum number of tokens that the model can generate." msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74 msgid "API URL" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:15 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15 msgid "Image size" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 msgid "Specify the size of the generated image, such as: 1024x1024" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41 msgid "Number of pictures" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 msgid "Specify the number of generated images" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Style" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Specify the style of generated images" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48 msgid "Default value, the image style is randomly output by the model" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49 msgid "photography" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50 msgid "Portraits" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51 msgid "3D cartoon" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52 msgid "animation" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53 msgid "painting" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54 msgid "watercolor" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55 msgid "sketch" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56 msgid "Chinese painting" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57 msgid "flat illustration" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 msgid "Timbre" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 msgid "Chinese sounds can support mixed scenes of Chinese and English" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26 msgid "Long Xiaochun" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27 msgid "Long Xiaoxia" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28 msgid "Long Xiaochen" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29 msgid "Long Xiaobai" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30 msgid "Long Laotie" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31 msgid "Long Shu" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32 msgid "Long Shuo" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33 msgid "Long Jing" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34 msgid "Long Miao" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35 msgid "Long Yue" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36 msgid "Long Yuan" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37 msgid "Long Fei" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38 msgid "Long Jielidou" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39 msgid "Long Tong" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40 msgid "Long Xiang" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "Speaking speed" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "[0.5, 2], the default is 1, usually one decimal place is enough" msgstr "" #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32 #: apps/models_provider/impl/azure_model_provider/credential/image.py:35 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:64 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:28 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:63 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:46 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:27 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/openai_model_provider/credential/image.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:53 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:27 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:66 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:27 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72 #: apps/models_provider/impl/xf_model_provider/credential/image.py:34 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:71 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:29 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:52 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:27 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45 #, python-brace-format msgid "{key} is required" msgstr "" #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:33 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:44 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:33 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:51 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32 msgid "Specify the maximum number of tokens that the model can generate" msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36 msgid "" "An update to Claude 2 that doubles the context window and improves " "reliability, hallucination rates, and evidence-based accuracy in long " "documents and RAG contexts." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43 msgid "" "Anthropic is a powerful model that can handle a variety of tasks, from " "complex dialogue and creative content generation to detailed command " "obedience." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50 msgid "" "The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-" "instant responsiveness. The model can answer simple queries and requests " "quickly. Customers will be able to build seamless AI experiences that mimic " "human interactions. Claude 3 Haiku can process images and return text " "output, and provides 200K context windows." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57 msgid "" "The Claude 3 Sonnet model from Anthropic strikes the ideal balance between " "intelligence and speed, especially when it comes to handling enterprise " "workloads. This model offers maximum utility while being priced lower than " "competing products, and it's been engineered to be a solid choice for " "deploying AI at scale." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64 msgid "" "The Claude 3.5 Sonnet raises the industry standard for intelligence, " "outperforming competing models and the Claude 3 Opus in extensive " "evaluations, with the speed and cost-effectiveness of our mid-range models." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71 msgid "" "A faster, more affordable but still very powerful model that can handle a " "range of tasks including casual conversation, text analysis, summarization " "and document question answering." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78 msgid "" "Titan Text Premier is the most powerful and advanced model in the Titan Text " "series, designed to deliver exceptional performance for a variety of " "enterprise applications. With its cutting-edge features, it delivers greater " "accuracy and outstanding results, making it an excellent choice for " "organizations looking for a top-notch text processing solution." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85 msgid "" "Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-" "tuning English-language tasks, including summarization and copywriting, " "where customers require smaller, more cost-effective, and highly " "customizable models." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91 msgid "" "Amazon Titan Text Express has context lengths of up to 8,000 tokens, making " "it ideal for a variety of high-level general language tasks, such as open-" "ended text generation and conversational chat, as well as support in " "retrieval-augmented generation (RAG). At launch, the model is optimized for " "English, but other languages are supported." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97 msgid "" "7B dense converter for rapid deployment and easy customization. Small in " "size yet powerful in a variety of use cases. Supports English and code, as " "well as 32k context windows." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103 msgid "" "Advanced Mistral AI large-scale language model capable of handling any " "language task, including complex multilingual reasoning, text understanding, " "transformation, and code generation." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109 msgid "" "Ideal for content creation, conversational AI, language understanding, R&D, " "and enterprise applications" msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115 msgid "" "Ideal for limited computing power and resources, edge devices, and faster " "training times." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123 msgid "" "Titan Embed Text is the largest embedding model in the Amazon Titan Embed " "series and can handle various text embedding tasks, such as text " "classification, text similarity calculation, etc." msgstr "" #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47 #, python-brace-format msgid "The following fields are required: {keys}" msgstr "" #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:76 msgid "Verification failed, please check whether the parameters are correct" msgstr "" #: apps/models_provider/impl/azure_model_provider/credential/tti.py:28 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28 msgid "Picture quality" msgstr "" #: apps/models_provider/impl/azure_model_provider/credential/tts.py:17 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:17 msgid "" "Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) " "to find one that suits your desired tone and audience. The current voiceover " "is optimized for English." msgstr "" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24 msgid "Good at common conversational tasks, supports 32K contexts" msgstr "" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29 msgid "Good at handling programming tasks, supports 16K contexts" msgstr "" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32 msgid "Latest Gemini 1.0 Pro model, updated with Google update" msgstr "" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36 msgid "Latest Gemini 1.0 Pro Vision model, updated with Google update" msgstr "" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58 msgid "Latest Gemini 1.5 Flash model, updated with Google updates" msgstr "" #: apps/models_provider/impl/gemini_model_provider/model/stt.py:53 msgid "convert audio to text" msgstr "" #: apps/models_provider/impl/local_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:54 msgid "Model catalog" msgstr "" #: apps/models_provider/impl/local_model_provider/local_model_provider.py:39 msgid "local model" msgstr "" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44 msgid "API domain name is invalid" msgstr "" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48 msgid "The model does not exist, please download the model first" msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 7B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 13B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 70B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68 msgid "" "Since the Chinese alignment of Llama2 itself is weak, we use the Chinese " "instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so " "that it has strong Chinese conversation capabilities." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72 msgid "" "Meta Llama 3: The most capable public product LLM to date. 8 billion " "parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76 msgid "" "Meta Llama 3: The most capable public product LLM to date. 70 billion " "parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80 msgid "" "Compared with previous versions, qwen 1.5 0.5b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 500 million parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84 msgid "" "Compared with previous versions, qwen 1.5 1.8b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 1.8 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88 msgid "" "Compared with previous versions, qwen 1.5 4b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "4 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93 msgid "" "Compared with previous versions, qwen 1.5 7b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "7 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97 msgid "" "Compared with previous versions, qwen 1.5 14b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "14 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101 msgid "" "Compared with previous versions, qwen 1.5 32b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "32 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105 msgid "" "Compared with previous versions, qwen 1.5 72b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "72 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109 msgid "" "Compared with previous versions, qwen 1.5 110b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 110 billion parameters." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193 msgid "" "Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open " "model." msgstr "" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197 msgid "" "A high-performance open embedding model with a large token context window." msgstr "" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:16 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 " "or 1792x1024 pixels." msgstr "" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 msgid "" " \n" "By default, images are produced in standard quality, but with DALL·E 3 you " "can set quality: \"hd\" to enhance detail. Square, standard quality images " "are generated fastest.\n" " " msgstr "" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44 msgid "" "You can use DALL·E 3 to request 1 image at a time (requesting more images by " "issuing parallel requests), or use DALL·E 2 with the n parameter to request " "up to 10 images at a time." msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119 #: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118 msgid "The latest gpt-3.5-turbo, updated with OpenAI adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38 msgid "Latest gpt-4, updated with OpenAI adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99 msgid "" "The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI " "adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102 msgid "" "The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI " "adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46 msgid "The latest gpt-4-turbo, updated with OpenAI adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49 msgid "The latest gpt-4-turbo-preview, updated with OpenAI adjustments" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53 msgid "" "gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 " "tokens" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57 msgid "" "gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 " "tokens" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61 msgid "" "[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June " "13, 2024" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65 msgid "" "gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69 msgid "" "gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 " "tokens" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72 msgid "" "gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 " "tokens" msgstr "" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75 msgid "" "gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 " "tokens" msgstr "" #: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58 msgid "Tencent Cloud" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88 #, python-brace-format msgid "{keys} is required" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "painting style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "If not passed, the default value is 201 (Japanese anime style)" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18 msgid "Not limited to style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19 msgid "ink painting" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20 msgid "concept art" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21 msgid "Oil painting 1" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22 msgid "Oil Painting 2 (Van Gogh)" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23 msgid "watercolor painting" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24 msgid "pixel art" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25 msgid "impasto style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26 msgid "illustration" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27 msgid "paper cut style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28 msgid "Impressionism 1 (Monet)" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29 msgid "Impressionism 2" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31 msgid "classical portraiture" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32 msgid "black and white sketch" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33 msgid "cyberpunk" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34 msgid "science fiction style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35 msgid "dark style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37 msgid "vaporwave" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38 msgid "Japanese animation" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39 msgid "monster style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40 msgid "Beautiful ancient style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41 msgid "retro anime" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42 msgid "Game cartoon hand drawing" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43 msgid "Universal realistic style" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "Generate image resolution" msgstr "" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "If not transmitted, the default value is 768:768." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38 msgid "" "The most effective version of the current hybrid model, the trillion-level " "parameter scale MOE-32K long article model. Reaching the absolute leading " "level on various benchmarks, with complex instructions and reasoning, " "complex mathematical capabilities, support for function call, and " "application focus optimization in fields such as multi-language translation, " "finance, law, and medical care" msgstr "" "The most effective version of the current hybrid model, the trillion-level " "parameter scale MOE-32K long article model. Reaching the absolute leading " "level on various benchmarks, with complex instructions and reasoning, " "complex mathematical capabilities, support for function call, and " "agent focus optimization in fields such as multi-language translation, " "finance, law, and medical care" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45 msgid "" "A better routing strategy is adopted to simultaneously alleviate the " "problems of load balancing and expert convergence. For long articles, the " "needle-in-a-haystack index reaches 99.9%" msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51 msgid "" "Upgraded to MOE structure, the context window is 256k, leading many open " "source models in multiple evaluation sets such as NLP, code, mathematics, " "industry, etc." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57 msgid "" "Hunyuan's latest version of the role-playing model, a role-playing model " "launched by Hunyuan's official fine-tuning training, is based on the Hunyuan " "model combined with the role-playing scene data set for additional training, " "and has better basic effects in role-playing scenes." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63 msgid "" "Hunyuan's latest MOE architecture FunctionCall model has been trained with " "high-quality FunctionCall data and has a context window of 32K, leading in " "multiple dimensions of evaluation indicators." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69 msgid "" "Hunyuan's latest code generation model, after training the base model with " "200B high-quality code data, and iterating on high-quality SFT data for half " "a year, the context long window length has been increased to 8K, and it " "ranks among the top in the automatic evaluation indicators of code " "generation in the five major languages; the five major languages In the " "manual high-quality evaluation of 10 comprehensive code tasks that consider " "all aspects, the performance is in the first echelon." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77 msgid "" "Tencent's Hunyuan Embedding interface can convert text into high-quality " "vector data. The vector dimension is 1024 dimensions." msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87 msgid "Mixed element visual model" msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94 msgid "Hunyuan graph model" msgstr "" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125 msgid "Tencent Hunyuan" msgstr "" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24 #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42 msgid "Facebook’s 125M parameter model" msgstr "" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25 msgid "BAAI’s 7B parameter model" msgstr "" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26 msgid "BAAI’s 13B parameter mode" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16 msgid "" "If the gap between width, height and 512 is too large, the picture rendering " "effect will be poor and the probability of excessive delay will increase " "significantly. Recommended ratio and corresponding width and height before " "super score: width*height" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15 msgid "timbre" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "speaking speed" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 msgid "[0.2,3], the default is 1, usually one decimal place is enough" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88 msgid "" "The user goes to the model inference page of Volcano Ark to create an " "inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call " "it." msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59 msgid "Universal 2.0-Vincent Diagram" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64 msgid "Universal 2.0Pro-Vincent Chart" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69 msgid "Universal 1.4-Vincent Chart" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74 msgid "Animation 1.3.0-Vincent Picture" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79 msgid "Animation 1.3.1-Vincent Picture" msgstr "" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113 msgid "volcano engine" msgstr "" #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51 #, python-brace-format msgid "{model_name} The model does not support" msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24 #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53 msgid "" "ERNIE-Bot-4 is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27 msgid "" "ERNIE-Bot is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30 msgid "" "ERNIE-Bot-turbo is a large language model independently developed by Baidu. " "It covers massive Chinese data, has stronger capabilities in dialogue Q&A, " "content creation and generation, and has a faster response speed." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33 msgid "" "BLOOMZ-7B is a well-known large language model in the industry. It was " "developed and open sourced by BigScience and can output text in 46 languages " "and 13 programming languages." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39 msgid "" "Llama-2-13b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning and knowledge application. " "Llama-2-13b-chat is a native open source version with balanced performance " "and effect, suitable for conversation scenarios." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42 msgid "" "Llama-2-70b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning, and knowledge application. " "Llama-2-70b-chat is a native open source version with high-precision effects." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45 msgid "" "The Chinese enhanced version developed by the Qianfan team based on " "Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-" "EVAL." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49 msgid "" "Embedding-V1 is a text representation model based on Baidu Wenxin large " "model technology. It can convert text into a vector form represented by " "numerical values and can be used in text retrieval, information " "recommendation, knowledge mining and other scenarios. Embedding-V1 provides " "the Embeddings interface, which can generate corresponding vector " "representations based on input content. You can call this interface to input " "text into the model and obtain the corresponding vector representation for " "subsequent text processing and analysis." msgstr "" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66 msgid "Thousand sails large model" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/image.py:42 msgid "Please outline this picture" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:15 msgid "Speaker" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:16 msgid "" "Speaker, optional value: Please go to the console to add a trial or purchase " "speaker. After adding, the speaker parameter value will be displayed." msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:21 msgid "iFlytek Xiaoyan" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:22 msgid "iFlytek Xujiu" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:23 msgid "iFlytek Xiaoping" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:24 msgid "iFlytek Xiaojing" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:25 msgid "iFlytek Xuxiaobao" msgstr "" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "Speech speed, optional value: [0-100], default is 50" msgstr "" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39 #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50 msgid "Chinese and English recognition" msgstr "" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66 msgid "iFlytek Spark" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. The dimensions of the image can be 1024x1024, 1024x1792, or " "1792x1024 pixels." msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29 msgid "" "By default, images are generated in standard quality, you can set quality: " "\"hd\" to enhance detail. Square, standard quality images are generated " "fastest." msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42 msgid "" "You can request 1 image at a time (requesting more images by making parallel " "requests), or up to 10 images at a time using the n parameter." msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20 msgid "Chinese female" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21 msgid "Chinese male" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22 msgid "Japanese male" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23 msgid "Cantonese female" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24 msgid "English female" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25 msgid "English male" msgstr "" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26 msgid "Korean female" msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37 msgid "" "Code Llama is a language model specifically designed for code generation." msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44 msgid "" " \n" "Code Llama Instruct is a fine-tuned version of Code Llama's instructions, " "designed to perform specific tasks.\n" " " msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53 msgid "" "Code Llama Python is a language model specifically designed for Python code " "generation." msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60 msgid "" "CodeQwen 1.5 is a language model for code generation with high performance." msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67 msgid "CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5." msgstr "" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74 msgid "Deepseek is a large-scale language model with 13 billion parameters." msgstr "" #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16 msgid "" "Image size, only cogview-3-plus supports this parameter. Optional range: " "[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the " "default is 1024x1024." msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34 msgid "" "Have strong multi-modal understanding capabilities. Able to understand up to " "five images simultaneously and supports video content understanding" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis (free)" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46 msgid "" "Quickly and accurately generate images based on user text descriptions. " "Resolution supports 1024x1024" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes (free)" msgstr "" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75 msgid "zhipu AI" msgstr "" #: apps/models_provider/serializers/model_apply_serializers.py:32 #: apps/models_provider/serializers/model_apply_serializers.py:37 msgid "vector text" msgstr "" #: apps/models_provider/serializers/model_apply_serializers.py:33 msgid "vector text list" msgstr "" #: apps/models_provider/serializers/model_apply_serializers.py:41 msgid "text" msgstr "" #: apps/models_provider/serializers/model_apply_serializers.py:42 msgid "metadata" msgstr "" #: apps/models_provider/serializers/model_apply_serializers.py:47 msgid "query" msgstr "" #: apps/models_provider/serializers/model_serializer.py:44 #: apps/models_provider/serializers/model_serializer.py:257 msgid "parameter configuration" msgstr "" #: apps/models_provider/serializers/model_serializer.py:45 #: apps/models_provider/serializers/model_serializer.py:222 #: apps/models_provider/serializers/model_serializer.py:258 msgid "certification information" msgstr "" #: apps/models_provider/serializers/model_serializer.py:118 msgid "Shared models cannot be deleted or modified" msgstr "" #: apps/models_provider/serializers/model_serializer.py:230 #: apps/models_provider/serializers/model_serializer.py:269 #, python-brace-format msgid "base model【{model_name}】already exists" msgstr "" #: apps/models_provider/serializers/model_serializer.py:309 msgid "Model saving failed" msgstr "" #: apps/models_provider/views/model.py:60 #: apps/models_provider/views/model.py:61 #: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55 #: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57 msgid "Create model" msgstr "" #: apps/models_provider/views/model.py:90 #: apps/models_provider/views/model.py:91 #: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84 #: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86 msgid "Query model list" msgstr "" #: apps/models_provider/views/model.py:107 #: apps/models_provider/views/model.py:108 #: apps/models_provider/views/model.py:109 #: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102 #: apps/shared/views/shared_model.py:103 msgid "Update model" msgstr "" #: apps/models_provider/views/model.py:125 #: apps/models_provider/views/model.py:126 #: apps/models_provider/views/model.py:127 #: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120 #: apps/shared/views/shared_model.py:121 msgid "Delete model" msgstr "" #: apps/models_provider/views/model.py:140 #: apps/models_provider/views/model.py:141 #: apps/models_provider/views/model.py:142 #: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134 #: apps/shared/views/shared_model.py:135 msgid "Query model details" msgstr "" #: apps/models_provider/views/model.py:155 #: apps/models_provider/views/model.py:156 #: apps/models_provider/views/model.py:157 #: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149 #: apps/shared/views/shared_model.py:150 msgid "Get model parameter form" msgstr "" #: apps/models_provider/views/model.py:167 #: apps/models_provider/views/model.py:168 #: apps/models_provider/views/model.py:169 #: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161 #: apps/shared/views/shared_model.py:162 msgid "Save model parameter form" msgstr "" #: apps/models_provider/views/model.py:187 #: apps/models_provider/views/model.py:189 #: apps/models_provider/views/model.py:191 #: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181 #: apps/shared/views/shared_model.py:183 msgid "" "Query model meta information, this interface does not carry authentication " "information" msgstr "" #: apps/models_provider/views/model.py:204 #: apps/models_provider/views/model.py:205 #: apps/models_provider/views/model.py:206 #: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197 #: apps/shared/views/shared_model.py:198 msgid "Pause model download" msgstr "" #: apps/models_provider/views/model.py:222 #: apps/models_provider/views/model.py:223 #: apps/models_provider/views/model.py:224 msgid "Get Share model" msgstr "" #: apps/models_provider/views/model_apply.py:25 #: apps/models_provider/views/model_apply.py:26 #: apps/models_provider/views/model_apply.py:27 #: apps/models_provider/views/model_apply.py:37 #: apps/models_provider/views/model_apply.py:38 #: apps/models_provider/views/model_apply.py:39 msgid "Vectorization documentation" msgstr "" #: apps/models_provider/views/model_apply.py:49 #: apps/models_provider/views/model_apply.py:50 #: apps/models_provider/views/model_apply.py:51 msgid "Reorder documents" msgstr "" #: apps/models_provider/views/provide.py:21 #: apps/models_provider/views/provide.py:22 #: apps/models_provider/views/provide.py:23 msgid "Get a list of model suppliers" msgstr "" #: apps/models_provider/views/provide.py:43 #: apps/models_provider/views/provide.py:44 #: apps/models_provider/views/provide.py:45 msgid "Get a list of model types" msgstr "" #: apps/models_provider/views/provide.py:57 #: apps/models_provider/views/provide.py:58 #: apps/models_provider/views/provide.py:59 msgid "Example of obtaining model list" msgstr "" #: apps/models_provider/views/provide.py:75 #: apps/models_provider/views/provide.py:76 #: apps/models_provider/views/provide.py:77 msgid "Get model default parameters" msgstr "" #: apps/models_provider/views/provide.py:92 #: apps/models_provider/views/provide.py:93 #: apps/models_provider/views/provide.py:94 msgid "Get the model creation form" msgstr "" #: apps/oss/serializers/file.py:80 msgid "File not found" msgstr "" #: apps/oss/views/file.py:21 apps/oss/views/file.py:22 #: apps/oss/views/file.py:23 msgid "Upload file" msgstr "" #: apps/oss/views/file.py:27 apps/oss/views/file.py:41 #: apps/oss/views/file.py:53 msgid "File" msgstr "" #: apps/oss/views/file.py:36 apps/oss/views/file.py:37 #: apps/oss/views/file.py:38 msgid "Get file" msgstr "" #: apps/oss/views/file.py:48 apps/oss/views/file.py:49 #: apps/oss/views/file.py:50 msgid "Delete file" msgstr "" #: apps/resource_manage/views/document.py:30 #: apps/resource_manage/views/document.py:31 #: apps/resource_manage/views/document.py:32 msgid "Create system knowledge" msgstr "" #: apps/resource_manage/views/document.py:36 #: apps/resource_manage/views/document.py:56 #: apps/resource_manage/views/document.py:83 #: apps/resource_manage/views/document.py:113 #: apps/resource_manage/views/document.py:130 #: apps/resource_manage/views/document.py:155 #: apps/resource_manage/views/document.py:183 #: apps/resource_manage/views/document.py:210 #: apps/resource_manage/views/document.py:237 #: apps/resource_manage/views/document.py:267 #: apps/resource_manage/views/document.py:296 #: apps/resource_manage/views/document.py:317 #: apps/resource_manage/views/document.py:345 #: apps/resource_manage/views/document.py:362 #: apps/resource_manage/views/document.py:383 #: apps/resource_manage/views/document.py:411 #: apps/resource_manage/views/document.py:435 #: apps/resource_manage/views/document.py:460 #: apps/resource_manage/views/document.py:485 #: apps/resource_manage/views/document.py:507 #: apps/resource_manage/views/document.py:530 #: apps/resource_manage/views/document.py:553 #: apps/resource_manage/views/document.py:571 #: apps/resource_manage/views/document.py:585 msgid "System Knowledge/Documentation" msgstr "" #: apps/resource_manage/views/document.py:51 #: apps/resource_manage/views/document.py:52 #: apps/resource_manage/views/document.py:53 msgid "Get system document" msgstr "" #: apps/resource_manage/views/document.py:77 #: apps/resource_manage/views/document.py:78 #: apps/resource_manage/views/document.py:79 msgid "Segmented system document" msgstr "" #: apps/resource_manage/views/document.py:108 #: apps/resource_manage/views/document.py:109 #: apps/resource_manage/views/document.py:110 msgid "Get a list of system segment IDs" msgstr "" #: apps/resource_manage/views/document.py:124 #: apps/resource_manage/views/document.py:125 #: apps/resource_manage/views/document.py:126 msgid "Cancel system tasks in batches" msgstr "" #: apps/resource_manage/views/document.py:149 #: apps/resource_manage/views/document.py:150 #: apps/resource_manage/views/document.py:151 msgid "Create system knowledges in batches" msgstr "" #: apps/resource_manage/views/document.py:177 #: apps/resource_manage/views/document.py:178 #: apps/resource_manage/views/document.py:179 msgid "Batch sync system knowledges" msgstr "" #: apps/resource_manage/views/document.py:204 #: apps/resource_manage/views/document.py:206 msgid "Delete system document in batches" msgstr "" #: apps/resource_manage/views/document.py:205 msgid "Delete system knowledge in batches" msgstr "" #: apps/resource_manage/views/document.py:232 #: apps/resource_manage/views/document.py:233 msgid "Batch refresh system document vector library" msgstr "" #: apps/resource_manage/views/document.py:261 #: apps/resource_manage/views/document.py:262 #: apps/resource_manage/views/document.py:263 msgid "Batch generate related system problems" msgstr "" #: apps/resource_manage/views/document.py:290 #: apps/resource_manage/views/document.py:291 #: apps/resource_manage/views/document.py:292 msgid "Modify system document hit processing methods in batches" msgstr "" #: apps/resource_manage/views/document.py:312 #: apps/resource_manage/views/document.py:313 msgid "Migrate system knowledges in batches" msgstr "" #: apps/resource_manage/views/document.py:340 #: apps/resource_manage/views/document.py:341 #: apps/resource_manage/views/document.py:342 msgid "Get system document details" msgstr "" #: apps/resource_manage/views/document.py:356 #: apps/resource_manage/views/document.py:357 #: apps/resource_manage/views/document.py:358 msgid "Modify system document" msgstr "" #: apps/resource_manage/views/document.py:378 #: apps/resource_manage/views/document.py:379 #: apps/resource_manage/views/document.py:380 msgid "Delete system document" msgstr "" #: apps/resource_manage/views/document.py:405 #: apps/resource_manage/views/document.py:406 #: apps/resource_manage/views/document.py:407 msgid "Synchronize system web site types" msgstr "" #: apps/resource_manage/views/document.py:429 #: apps/resource_manage/views/document.py:430 #: apps/resource_manage/views/document.py:431 msgid "Refresh system knowledge vector library" msgstr "" #: apps/resource_manage/views/document.py:454 #: apps/resource_manage/views/document.py:455 #: apps/resource_manage/views/document.py:456 msgid "Cancel system task" msgstr "" #: apps/resource_manage/views/document.py:480 #: apps/resource_manage/views/document.py:481 #: apps/resource_manage/views/document.py:482 msgid "Get system document by pagination" msgstr "" #: apps/resource_manage/views/document.py:503 #: apps/resource_manage/views/document.py:504 msgid "Export system knowledge" msgstr "" #: apps/resource_manage/views/document.py:526 #: apps/resource_manage/views/document.py:527 msgid "Export Zip system knowledge" msgstr "" #: apps/resource_manage/views/document.py:549 #: apps/resource_manage/views/document.py:550 msgid "Download system source file" msgstr "" #: apps/resource_manage/views/document.py:567 #: apps/resource_manage/views/document.py:568 msgid "Get system QA template" msgstr "" #: apps/resource_manage/views/document.py:581 #: apps/resource_manage/views/document.py:582 msgid "Get system form template" msgstr "" #: apps/resource_manage/views/knowledge.py:26 #: apps/resource_manage/views/knowledge.py:27 #: apps/resource_manage/views/knowledge.py:28 msgid "Get system knowledge list" msgstr "" #: apps/resource_manage/views/knowledge.py:31 #: apps/resource_manage/views/knowledge.py:50 #: apps/resource_manage/views/knowledge.py:72 #: apps/resource_manage/views/knowledge.py:87 #: apps/resource_manage/views/knowledge.py:102 #: apps/resource_manage/views/knowledge.py:121 #: apps/resource_manage/views/knowledge.py:147 #: apps/resource_manage/views/knowledge.py:174 #: apps/resource_manage/views/knowledge.py:192 #: apps/resource_manage/views/knowledge.py:210 #: apps/resource_manage/views/knowledge.py:231 #: apps/resource_manage/views/knowledge.py:252 #: apps/resource_manage/views/knowledge.py:272 msgid "System Knowledge" msgstr "" #: apps/resource_manage/views/knowledge.py:45 #: apps/resource_manage/views/knowledge.py:46 #: apps/resource_manage/views/knowledge.py:47 msgid "Get system knowledge list by pagination" msgstr "" #: apps/resource_manage/views/knowledge.py:66 #: apps/resource_manage/views/knowledge.py:67 #: apps/resource_manage/views/knowledge.py:68 msgid "Update system knowledge" msgstr "" #: apps/resource_manage/views/knowledge.py:82 #: apps/resource_manage/views/knowledge.py:83 #: apps/resource_manage/views/knowledge.py:84 msgid "Get system knowledge" msgstr "" #: apps/resource_manage/views/knowledge.py:97 #: apps/resource_manage/views/knowledge.py:98 #: apps/resource_manage/views/knowledge.py:99 msgid "Delete system knowledge" msgstr "" #: apps/resource_manage/views/knowledge.py:115 #: apps/resource_manage/views/knowledge.py:116 #: apps/resource_manage/views/knowledge.py:117 msgid "Synchronize the system knowledge base of the website" msgstr "" #: apps/resource_manage/views/knowledge.py:141 #: apps/resource_manage/views/knowledge.py:142 #: apps/resource_manage/views/knowledge.py:143 msgid "System Hit test list" msgstr "" #: apps/resource_manage/views/knowledge.py:168 #: apps/resource_manage/views/knowledge.py:169 #: apps/resource_manage/views/knowledge.py:170 msgid "System Re-vectorize" msgstr "" #: apps/resource_manage/views/knowledge.py:188 #: apps/resource_manage/views/knowledge.py:189 msgid "Export system knowledge base" msgstr "" #: apps/resource_manage/views/knowledge.py:206 #: apps/resource_manage/views/knowledge.py:207 msgid "Export system knowledge base containing images" msgstr "" #: apps/resource_manage/views/knowledge.py:225 #: apps/resource_manage/views/knowledge.py:226 #: apps/resource_manage/views/knowledge.py:227 msgid "System generate related" msgstr "" #: apps/resource_manage/views/knowledge.py:247 #: apps/resource_manage/views/knowledge.py:248 #: apps/resource_manage/views/knowledge.py:249 msgid "Get model for system knowledge base" msgstr "" #: apps/resource_manage/views/knowledge.py:267 #: apps/resource_manage/views/knowledge.py:268 #: apps/resource_manage/views/knowledge.py:269 msgid "Get embedding model for system knowledge base" msgstr "" #: apps/resource_manage/views/paragraph.py:24 #: apps/resource_manage/views/paragraph.py:25 #: apps/resource_manage/views/paragraph.py:26 msgid "System paragraph list" msgstr "" #: apps/resource_manage/views/paragraph.py:29 #: apps/resource_manage/views/paragraph.py:50 #: apps/resource_manage/views/paragraph.py:76 #: apps/resource_manage/views/paragraph.py:93 #: apps/resource_manage/views/paragraph.py:125 #: apps/resource_manage/views/paragraph.py:151 #: apps/resource_manage/views/paragraph.py:179 #: apps/resource_manage/views/paragraph.py:201 #: apps/resource_manage/views/paragraph.py:233 #: apps/resource_manage/views/paragraph.py:259 #: apps/resource_manage/views/paragraph.py:283 #: apps/resource_manage/views/paragraph.py:314 #: apps/resource_manage/views/paragraph.py:344 #: apps/resource_manage/views/paragraph.py:370 msgid "System Knowledge/Documentation/Paragraph" msgstr "" #: apps/resource_manage/views/paragraph.py:45 #: apps/resource_manage/views/paragraph.py:46 msgid "Create system paragraph" msgstr "" #: apps/resource_manage/views/paragraph.py:70 #: apps/resource_manage/views/paragraph.py:71 #: apps/resource_manage/views/paragraph.py:72 msgid "Batch system paragraph" msgstr "" #: apps/resource_manage/views/paragraph.py:88 #: apps/resource_manage/views/paragraph.py:89 msgid "Migrate system paragraphs in batches" msgstr "" #: apps/resource_manage/views/paragraph.py:119 #: apps/resource_manage/views/paragraph.py:120 #: apps/resource_manage/views/paragraph.py:121 msgid "Batch generate system related" msgstr "" #: apps/resource_manage/views/paragraph.py:145 #: apps/resource_manage/views/paragraph.py:146 #: apps/resource_manage/views/paragraph.py:147 msgid "Modify system paragraph data" msgstr "" #: apps/resource_manage/views/paragraph.py:174 #: apps/resource_manage/views/paragraph.py:175 #: apps/resource_manage/views/paragraph.py:176 msgid "Get system paragraph details" msgstr "" #: apps/resource_manage/views/paragraph.py:196 #: apps/resource_manage/views/paragraph.py:197 #: apps/resource_manage/views/paragraph.py:198 msgid "Delete system paragraph" msgstr "" #: apps/resource_manage/views/paragraph.py:227 #: apps/resource_manage/views/paragraph.py:228 #: apps/resource_manage/views/paragraph.py:229 msgid "Add system associated questions" msgstr "" #: apps/resource_manage/views/paragraph.py:254 #: apps/resource_manage/views/paragraph.py:255 #: apps/resource_manage/views/paragraph.py:256 msgid "Get a list of system paragraph questions" msgstr "" #: apps/resource_manage/views/paragraph.py:277 #: apps/resource_manage/views/paragraph.py:278 #: apps/resource_manage/views/paragraph.py:279 msgid "Disassociation system issue" msgstr "" #: apps/resource_manage/views/paragraph.py:308 #: apps/resource_manage/views/paragraph.py:309 #: apps/resource_manage/views/paragraph.py:310 msgid "Related system questions" msgstr "" #: apps/resource_manage/views/paragraph.py:339 #: apps/resource_manage/views/paragraph.py:340 #: apps/resource_manage/views/paragraph.py:341 msgid "Get system paragraph list by pagination" msgstr "" #: apps/resource_manage/views/problem.py:23 #: apps/resource_manage/views/problem.py:24 #: apps/resource_manage/views/problem.py:25 msgid "System question list" msgstr "" #: apps/resource_manage/views/problem.py:28 #: apps/resource_manage/views/problem.py:50 #: apps/resource_manage/views/problem.py:71 #: apps/resource_manage/views/problem.py:94 #: apps/resource_manage/views/problem.py:115 #: apps/resource_manage/views/problem.py:135 #: apps/resource_manage/views/problem.py:158 #: apps/resource_manage/views/problem.py:182 msgid "System Knowledge/Documentation/Paragraph/Question" msgstr "" #: apps/resource_manage/views/problem.py:44 #: apps/resource_manage/views/problem.py:45 #: apps/resource_manage/views/problem.py:46 msgid "Create system question" msgstr "" #: apps/resource_manage/views/problem.py:66 #: apps/resource_manage/views/problem.py:67 #: apps/resource_manage/views/problem.py:68 msgid "Get a list of associated system paragraphs" msgstr "" #: apps/resource_manage/views/problem.py:88 #: apps/resource_manage/views/problem.py:89 #: apps/resource_manage/views/problem.py:90 msgid "Batch associated system paragraphs" msgstr "" #: apps/resource_manage/views/problem.py:109 #: apps/resource_manage/views/problem.py:110 #: apps/resource_manage/views/problem.py:111 msgid "Batch deletion system issues" msgstr "" #: apps/resource_manage/views/problem.py:130 #: apps/resource_manage/views/problem.py:131 #: apps/resource_manage/views/problem.py:132 msgid "Delete system question" msgstr "" #: apps/resource_manage/views/problem.py:152 #: apps/resource_manage/views/problem.py:153 #: apps/resource_manage/views/problem.py:154 msgid "Modify system question" msgstr "" #: apps/resource_manage/views/problem.py:177 #: apps/resource_manage/views/problem.py:178 #: apps/resource_manage/views/problem.py:179 msgid "Get the list of system questions by page" msgstr "" #: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25 #: apps/resource_manage/views/tool.py:26 msgid "Get system tool" msgstr "" #: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50 #: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80 #: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119 #: apps/resource_manage/views/tool.py:137 #: apps/resource_manage/views/tool.py:156 #: apps/resource_manage/views/tool.py:179 msgid "System Tool" msgstr "" #: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45 #: apps/resource_manage/views/tool.py:46 msgid "Update system tool" msgstr "" #: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61 #: apps/resource_manage/views/tool.py:62 msgid "Get system tool by id" msgstr "" #: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76 #: apps/resource_manage/views/tool.py:77 msgid "Delete system tool" msgstr "" #: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94 #: apps/resource_manage/views/tool.py:95 msgid "Get system tool list by pagination" msgstr "" #: apps/resource_manage/views/tool.py:114 #: apps/resource_manage/views/tool.py:115 #: apps/resource_manage/views/tool.py:116 msgid "Export system tool" msgstr "" #: apps/resource_manage/views/tool.py:132 #: apps/resource_manage/views/tool.py:133 #: apps/resource_manage/views/tool.py:134 msgid "Debug system tool" msgstr "" #: apps/resource_manage/views/tool.py:150 #: apps/resource_manage/views/tool.py:151 #: apps/resource_manage/views/tool.py:152 msgid "Check system code" msgstr "" #: apps/resource_manage/views/tool.py:173 #: apps/resource_manage/views/tool.py:174 #: apps/resource_manage/views/tool.py:175 msgid "Edit system tool icon" msgstr "" #: apps/role_setting/api/role_setting.py:16 #: apps/role_setting/api/role_setting.py:22 #: apps/role_setting/api/role_setting.py:33 #: apps/role_setting/api/role_setting.py:143 #: apps/role_setting/serializers/role_setting_serializers.py:193 #: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104 msgid "ID" msgstr "" #: apps/role_setting/api/role_setting.py:17 #: apps/role_setting/api/role_setting.py:23 #: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258 #: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235 msgid "Name" msgstr "" #: apps/role_setting/api/role_setting.py:18 #: apps/role_setting/api/role_setting.py:29 #: apps/role_setting/serializers/role_setting_serializers.py:194 msgid "Enable" msgstr "" #: apps/role_setting/api/role_setting.py:26 msgid "Permission" msgstr "" #: apps/role_setting/api/role_setting.py:37 msgid "Children" msgstr "" #: apps/role_setting/api/role_setting.py:55 #: apps/role_setting/serializers/role_setting_serializers.py:107 msgid "Role type" msgstr "" #: apps/role_setting/api/role_setting.py:76 msgid "Internal role" msgstr "" #: apps/role_setting/api/role_setting.py:80 msgid "Custom role" msgstr "" #: apps/role_setting/api/role_setting.py:108 #: apps/role_setting/api/role_setting.py:128 #: apps/role_setting/api/role_setting.py:164 #: apps/role_setting/serializers/role_setting_serializers.py:110 #: apps/role_setting/serializers/role_setting_serializers.py:329 #: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86 msgid "Role ID" msgstr "" #: apps/role_setting/api/role_setting.py:135 msgid "User relation ID" msgstr "" #: apps/role_setting/api/role_setting.py:145 #: apps/role_setting/api/role_setting.py:185 #: apps/role_setting/serializers/role_setting_serializers.py:330 #: apps/users/api/user.py:77 apps/users/serializers/login.py:27 #: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114 #: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124 #: apps/workspace/serializers/workspace_serializers.py:240 #: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105 #: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62 #: apps/xpack/serializers/chat_user.py:564 msgid "Username" msgstr "" #: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84 #: apps/xpack/api/chat_user.py:106 msgid "Nickname" msgstr "" #: apps/role_setting/api/role_setting.py:148 msgid "Workspace Name" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:104 msgid "Role name" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:122 #: apps/role_setting/serializers/role_setting_serializers.py:200 #: apps/role_setting/serializers/role_setting_serializers.py:325 #: apps/role_setting/serializers/role_setting_serializers.py:337 msgid "Role does not exist" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:124 #: apps/role_setting/serializers/role_setting_serializers.py:202 msgid "Cannot modify built-in role" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:132 msgid "Role name already exists" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:152 msgid "Invalid role type" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:204 msgid "Cannot delete built-in role" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:262 #: apps/users/api/user.py:135 apps/users/serializers/user.py:471 #: apps/workspace/serializers/workspace_serializers.py:161 #: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158 #: apps/xpack/serializers/chat_user.py:172 #: apps/xpack/serializers/chat_user.py:502 msgid "User IDs" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:267 #: apps/users/api/user.py:30 msgid "Workspace IDs" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:272 #: apps/workspace/serializers/workspace_serializers.py:172 msgid "Members" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:312 #: apps/workspace/serializers/workspace_serializers.py:223 msgid "User relation does not exist" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:316 #: apps/workspace/serializers/workspace_serializers.py:226 msgid "Cannot remove member from built-in role" msgstr "" #: apps/role_setting/serializers/role_setting_serializers.py:370 msgid "Only update members to normal users" msgstr "" #: apps/role_setting/views/role_setting.py:39 #: apps/role_setting/views/role_setting.py:40 #: apps/role_setting/views/role_setting.py:41 msgid "Get role permission template" msgstr "" #: apps/role_setting/views/role_setting.py:62 #: apps/role_setting/views/role_setting.py:63 #: apps/role_setting/views/role_setting.py:64 msgid "Create or update role" msgstr "" #: apps/role_setting/views/role_setting.py:80 #: apps/role_setting/views/role_setting.py:81 #: apps/role_setting/views/role_setting.py:82 msgid "Get role list" msgstr "" #: apps/role_setting/views/role_setting.py:98 #: apps/role_setting/views/role_setting.py:99 #: apps/role_setting/views/role_setting.py:100 msgid "Delete role" msgstr "" #: apps/role_setting/views/role_setting.py:120 #: apps/role_setting/views/role_setting.py:121 #: apps/role_setting/views/role_setting.py:122 msgid "Create or update role permission" msgstr "" #: apps/role_setting/views/role_setting.py:140 #: apps/role_setting/views/role_setting.py:141 #: apps/role_setting/views/role_setting.py:142 msgid "Get role permission" msgstr "" #: apps/role_setting/views/role_setting.py:161 #: apps/role_setting/views/role_setting.py:162 #: apps/role_setting/views/role_setting.py:163 msgid "Add member to system role" msgstr "" #: apps/role_setting/views/role_setting.py:186 #: apps/role_setting/views/role_setting.py:187 #: apps/role_setting/views/role_setting.py:188 msgid "Remove member from system role" msgstr "" #: apps/role_setting/views/role_setting.py:205 #: apps/role_setting/views/role_setting.py:206 #: apps/role_setting/views/role_setting.py:207 msgid "Get system role member list" msgstr "" #: apps/role_setting/views/role_setting.py:223 #: apps/role_setting/views/role_setting.py:224 #: apps/role_setting/views/role_setting.py:225 msgid "Get Workspace role list" msgstr "" #: apps/role_setting/views/role_setting.py:227 #: apps/role_setting/views/role_setting.py:248 #: apps/role_setting/views/role_setting.py:273 #: apps/role_setting/views/role_setting.py:292 msgid "Workspace Role" msgstr "" #: apps/role_setting/views/role_setting.py:242 #: apps/role_setting/views/role_setting.py:243 #: apps/role_setting/views/role_setting.py:244 msgid "Add member to workspace role" msgstr "" #: apps/role_setting/views/role_setting.py:268 #: apps/role_setting/views/role_setting.py:269 #: apps/role_setting/views/role_setting.py:270 msgid "Remove member from workspace role" msgstr "" #: apps/role_setting/views/role_setting.py:287 #: apps/role_setting/views/role_setting.py:288 #: apps/role_setting/views/role_setting.py:289 msgid "Get workspace role member list" msgstr "" #: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54 msgid "Folder token" msgstr "" #: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46 #: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133 #: apps/shared/serializers/shared_tool.py:43 #: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142 #: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454 msgid "tool name" msgstr "" #: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53 #: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140 #: apps/shared/serializers/shared_tool.py:44 #: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144 #: apps/tools/serializers/tool.py:159 msgid "tool description" msgstr "" #: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184 #: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288 #: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66 #: apps/shared/serializers/shared_tool.py:107 #: apps/tools/serializers/tool.py:267 msgid "tool id" msgstr "" #: apps/shared/serializers/shared_knowledge.py:35 #: apps/shared/serializers/shared_model.py:20 #: apps/shared/serializers/shared_tool.py:19 msgid "workspace id list" msgstr "" #: apps/shared/serializers/shared_knowledge.py:36 #: apps/shared/serializers/shared_model.py:21 #: apps/shared/serializers/shared_tool.py:20 msgid "authentication type" msgstr "" #: apps/shared/serializers/shared_knowledge.py:196 #: apps/shared/serializers/shared_knowledge.py:216 #: apps/shared/serializers/shared_knowledge.py:236 msgid "Knowledge does not exist" msgstr "" #: apps/shared/serializers/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:238 msgid "Only shared knowledge can be authorized" msgstr "" #: apps/shared/serializers/shared_tool.py:74 msgid "Only shared tools can be deleted" msgstr "" #: apps/shared/serializers/shared_tool.py:76 msgid "System tools cannot be deleted" msgstr "" #: apps/shared/serializers/shared_tool.py:118 #: apps/shared/serializers/shared_tool.py:138 msgid "Tool does not exist" msgstr "" #: apps/shared/serializers/shared_tool.py:120 #: apps/shared/serializers/shared_tool.py:140 msgid "Only shared tools can be authorized" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:25 #: apps/shared/views/shared_dataset_lark_views.py:26 #: apps/shared/views/shared_dataset_lark_views.py:27 #: apps/xpack/views/dataset_lark_views.py:23 #: apps/xpack/views/dataset_lark_views.py:24 #: apps/xpack/views/dataset_lark_views.py:25 msgid "Create a lark knowledge base" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:44 #: apps/shared/views/shared_dataset_lark_views.py:45 #: apps/shared/views/shared_dataset_lark_views.py:46 #: apps/xpack/views/dataset_lark_views.py:44 #: apps/xpack/views/dataset_lark_views.py:45 #: apps/xpack/views/dataset_lark_views.py:46 msgid "Update a lark knowledge base" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:67 #: apps/shared/views/shared_dataset_lark_views.py:68 #: apps/shared/views/shared_dataset_lark_views.py:69 #: apps/xpack/views/dataset_lark_views.py:67 #: apps/xpack/views/dataset_lark_views.py:68 #: apps/xpack/views/dataset_lark_views.py:69 msgid "Get document list from lark" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:72 #: apps/shared/views/shared_dataset_lark_views.py:90 #: apps/shared/views/shared_dataset_lark_views.py:109 #: apps/shared/views/shared_dataset_lark_views.py:129 #: apps/shared/views/shared_document.py:36 #: apps/shared/views/shared_document.py:56 #: apps/shared/views/shared_document.py:83 #: apps/shared/views/shared_document.py:114 #: apps/shared/views/shared_document.py:131 #: apps/shared/views/shared_document.py:156 #: apps/shared/views/shared_document.py:184 #: apps/shared/views/shared_document.py:211 #: apps/shared/views/shared_document.py:238 #: apps/shared/views/shared_document.py:268 #: apps/shared/views/shared_document.py:297 #: apps/shared/views/shared_document.py:318 #: apps/shared/views/shared_document.py:346 #: apps/shared/views/shared_document.py:363 #: apps/shared/views/shared_document.py:384 #: apps/shared/views/shared_document.py:412 #: apps/shared/views/shared_document.py:436 #: apps/shared/views/shared_document.py:461 #: apps/shared/views/shared_document.py:486 #: apps/shared/views/shared_document.py:508 #: apps/shared/views/shared_document.py:531 #: apps/shared/views/shared_document.py:554 #: apps/shared/views/shared_document.py:575 #: apps/shared/views/shared_document.py:602 #: apps/shared/views/shared_document.py:629 #: apps/shared/views/shared_document.py:651 #: apps/shared/views/shared_document.py:665 msgid "Shared Knowledge/Documentation" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:85 #: apps/shared/views/shared_dataset_lark_views.py:86 #: apps/shared/views/shared_dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:86 #: apps/xpack/views/dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:88 msgid "Import documents to the lark knowledge base" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:104 #: apps/shared/views/shared_dataset_lark_views.py:105 #: apps/shared/views/shared_dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:107 #: apps/xpack/views/dataset_lark_views.py:108 msgid "Synchronize lark document" msgstr "" #: apps/shared/views/shared_dataset_lark_views.py:123 #: apps/shared/views/shared_dataset_lark_views.py:124 #: apps/shared/views/shared_dataset_lark_views.py:125 #: apps/xpack/views/dataset_lark_views.py:126 #: apps/xpack/views/dataset_lark_views.py:127 #: apps/xpack/views/dataset_lark_views.py:128 msgid "Batch synchronize lark document" msgstr "" #: apps/shared/views/shared_document.py:30 #: apps/shared/views/shared_document.py:31 #: apps/shared/views/shared_document.py:32 msgid "Create shared document" msgstr "" #: apps/shared/views/shared_document.py:51 #: apps/shared/views/shared_document.py:52 #: apps/shared/views/shared_document.py:53 msgid "Get shared document" msgstr "" #: apps/shared/views/shared_document.py:77 #: apps/shared/views/shared_document.py:78 #: apps/shared/views/shared_document.py:79 msgid "Segmented shared document" msgstr "" #: apps/shared/views/shared_document.py:109 #: apps/shared/views/shared_document.py:110 #: apps/shared/views/shared_document.py:111 msgid "Get a list of shared segment IDs" msgstr "" #: apps/shared/views/shared_document.py:125 #: apps/shared/views/shared_document.py:126 #: apps/shared/views/shared_document.py:127 msgid "Cancel shared tasks in batches" msgstr "" #: apps/shared/views/shared_document.py:150 #: apps/shared/views/shared_document.py:151 #: apps/shared/views/shared_document.py:152 msgid "Create shared documents in batches" msgstr "" #: apps/shared/views/shared_document.py:178 #: apps/shared/views/shared_document.py:179 #: apps/shared/views/shared_document.py:180 msgid "Batch sync shared documents" msgstr "" #: apps/shared/views/shared_document.py:205 #: apps/shared/views/shared_document.py:206 #: apps/shared/views/shared_document.py:207 msgid "Delete shared documents in batches" msgstr "" #: apps/shared/views/shared_document.py:233 #: apps/shared/views/shared_document.py:234 msgid "Batch refresh shared document vector library" msgstr "" #: apps/shared/views/shared_document.py:262 #: apps/shared/views/shared_document.py:263 #: apps/shared/views/shared_document.py:264 msgid "Batch generate related shared problems" msgstr "" #: apps/shared/views/shared_document.py:291 #: apps/shared/views/shared_document.py:292 #: apps/shared/views/shared_document.py:293 msgid "Modify shared document hit processing methods in batches" msgstr "" #: apps/shared/views/shared_document.py:313 #: apps/shared/views/shared_document.py:314 msgid "Migrate shared documents in batches" msgstr "" #: apps/shared/views/shared_document.py:341 #: apps/shared/views/shared_document.py:342 #: apps/shared/views/shared_document.py:343 msgid "Get shared document details" msgstr "" #: apps/shared/views/shared_document.py:357 #: apps/shared/views/shared_document.py:358 #: apps/shared/views/shared_document.py:359 msgid "Modify shared document" msgstr "" #: apps/shared/views/shared_document.py:379 #: apps/shared/views/shared_document.py:380 #: apps/shared/views/shared_document.py:381 msgid "Delete shared document" msgstr "" #: apps/shared/views/shared_document.py:406 #: apps/shared/views/shared_document.py:407 #: apps/shared/views/shared_document.py:408 msgid "Synchronize shared web site types" msgstr "" #: apps/shared/views/shared_document.py:430 #: apps/shared/views/shared_document.py:431 #: apps/shared/views/shared_document.py:432 msgid "Refresh shared document vector library" msgstr "" #: apps/shared/views/shared_document.py:455 #: apps/shared/views/shared_document.py:456 #: apps/shared/views/shared_document.py:457 msgid "Cancel shared task" msgstr "" #: apps/shared/views/shared_document.py:481 #: apps/shared/views/shared_document.py:482 #: apps/shared/views/shared_document.py:483 msgid "Get shared document by pagination" msgstr "" #: apps/shared/views/shared_document.py:504 #: apps/shared/views/shared_document.py:505 msgid "Export shared document" msgstr "" #: apps/shared/views/shared_document.py:527 #: apps/shared/views/shared_document.py:528 msgid "Export Zip shared document" msgstr "" #: apps/shared/views/shared_document.py:550 #: apps/shared/views/shared_document.py:551 msgid "Download shared source file" msgstr "" #: apps/shared/views/shared_document.py:569 #: apps/shared/views/shared_document.py:571 msgid "Create Web site shared documents" msgstr "" #: apps/shared/views/shared_document.py:596 #: apps/shared/views/shared_document.py:597 #: apps/shared/views/shared_document.py:598 msgid "Import QA and create shared documentation" msgstr "" #: apps/shared/views/shared_document.py:623 #: apps/shared/views/shared_document.py:624 #: apps/shared/views/shared_document.py:625 msgid "Import tables and create shared documents" msgstr "" #: apps/shared/views/shared_document.py:647 #: apps/shared/views/shared_document.py:648 msgid "Get shared QA template" msgstr "" #: apps/shared/views/shared_document.py:661 #: apps/shared/views/shared_document.py:662 msgid "Get shared form template" msgstr "" #: apps/shared/views/shared_knowledge.py:28 #: apps/shared/views/shared_knowledge.py:29 #: apps/shared/views/shared_knowledge.py:30 msgid "Get share resource knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:48 #: apps/shared/views/shared_knowledge.py:49 #: apps/shared/views/shared_knowledge.py:50 msgid "Get shared knowledge list by pagination" msgstr "" #: apps/shared/views/shared_knowledge.py:70 #: apps/shared/views/shared_knowledge.py:71 #: apps/shared/views/shared_knowledge.py:72 msgid "Update shared knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:86 #: apps/shared/views/shared_knowledge.py:87 #: apps/shared/views/shared_knowledge.py:88 msgid "Get shared knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:101 #: apps/shared/views/shared_knowledge.py:102 #: apps/shared/views/shared_knowledge.py:103 msgid "Delete shared knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:119 #: apps/shared/views/shared_knowledge.py:120 #: apps/shared/views/shared_knowledge.py:121 msgid "Synchronize the shared knowledge base of the website" msgstr "" #: apps/shared/views/shared_knowledge.py:145 #: apps/shared/views/shared_knowledge.py:146 #: apps/shared/views/shared_knowledge.py:147 msgid "Shared Hit test list" msgstr "" #: apps/shared/views/shared_knowledge.py:172 #: apps/shared/views/shared_knowledge.py:173 #: apps/shared/views/shared_knowledge.py:174 msgid "Shared Re-vectorize" msgstr "" #: apps/shared/views/shared_knowledge.py:192 #: apps/shared/views/shared_knowledge.py:193 msgid "Export shared knowledge base" msgstr "" #: apps/shared/views/shared_knowledge.py:210 #: apps/shared/views/shared_knowledge.py:211 msgid "Export shared knowledge base containing images" msgstr "" #: apps/shared/views/shared_knowledge.py:229 #: apps/shared/views/shared_knowledge.py:230 #: apps/shared/views/shared_knowledge.py:231 msgid "Shared generate related" msgstr "" #: apps/shared/views/shared_knowledge.py:251 #: apps/shared/views/shared_knowledge.py:252 #: apps/shared/views/shared_knowledge.py:253 msgid "Get model for shared knowledge base" msgstr "" #: apps/shared/views/shared_knowledge.py:271 #: apps/shared/views/shared_knowledge.py:272 #: apps/shared/views/shared_knowledge.py:273 msgid "Get embedding model for shared knowledge base" msgstr "" #: apps/shared/views/shared_knowledge.py:291 #: apps/shared/views/shared_knowledge.py:293 msgid "Authorization knowledge workspace" msgstr "" #: apps/shared/views/shared_knowledge.py:292 msgid "Authorization knowledge workspace " msgstr "" #: apps/shared/views/shared_knowledge.py:307 #: apps/shared/views/shared_knowledge.py:308 #: apps/shared/views/shared_knowledge.py:309 msgid "Get Authorization knowledge workspace" msgstr "" #: apps/shared/views/shared_knowledge.py:326 #: apps/shared/views/shared_knowledge.py:327 #: apps/shared/views/shared_knowledge.py:328 msgid "Create shared base knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:349 #: apps/shared/views/shared_knowledge.py:350 #: apps/shared/views/shared_knowledge.py:351 msgid "Create shared web knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:381 #: apps/shared/views/shared_knowledge.py:382 #: apps/shared/views/shared_knowledge.py:383 msgid "Get shared workspace knowledge" msgstr "" #: apps/shared/views/shared_knowledge.py:402 #: apps/shared/views/shared_knowledge.py:403 #: apps/shared/views/shared_knowledge.py:404 msgid "Get shared workspace knowledge list by pagination" msgstr "" #: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215 msgid "Authorization model workspace" msgstr "" #: apps/shared/views/shared_model.py:214 msgid "Authorization model workspace " msgstr "" #: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230 #: apps/shared/views/shared_model.py:231 msgid "Get Authorization model workspace" msgstr "" #: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249 #: apps/shared/views/shared_model.py:250 msgid "Get Share model by workspace id" msgstr "" #: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266 #: apps/shared/views/shared_model.py:267 msgid "Get Share model by workspace id with pagination" msgstr "" #: apps/shared/views/shared_paragraph.py:24 #: apps/shared/views/shared_paragraph.py:25 #: apps/shared/views/shared_paragraph.py:26 msgid "Shared paragraph list" msgstr "" #: apps/shared/views/shared_paragraph.py:29 #: apps/shared/views/shared_paragraph.py:50 #: apps/shared/views/shared_paragraph.py:76 #: apps/shared/views/shared_paragraph.py:93 #: apps/shared/views/shared_paragraph.py:125 #: apps/shared/views/shared_paragraph.py:151 #: apps/shared/views/shared_paragraph.py:179 #: apps/shared/views/shared_paragraph.py:201 #: apps/shared/views/shared_paragraph.py:233 #: apps/shared/views/shared_paragraph.py:259 #: apps/shared/views/shared_paragraph.py:283 #: apps/shared/views/shared_paragraph.py:314 #: apps/shared/views/shared_paragraph.py:345 #: apps/shared/views/shared_paragraph.py:371 msgid "Shared Knowledge/Documentation/Paragraph" msgstr "" #: apps/shared/views/shared_paragraph.py:45 #: apps/shared/views/shared_paragraph.py:46 msgid "Create shared paragraph" msgstr "" #: apps/shared/views/shared_paragraph.py:70 #: apps/shared/views/shared_paragraph.py:71 #: apps/shared/views/shared_paragraph.py:72 msgid "Batch shared paragraph" msgstr "" #: apps/shared/views/shared_paragraph.py:88 #: apps/shared/views/shared_paragraph.py:89 msgid "Migrate shared paragraphs in batches" msgstr "" #: apps/shared/views/shared_paragraph.py:119 #: apps/shared/views/shared_paragraph.py:120 #: apps/shared/views/shared_paragraph.py:121 msgid "Batch generate shared related" msgstr "" #: apps/shared/views/shared_paragraph.py:145 #: apps/shared/views/shared_paragraph.py:146 #: apps/shared/views/shared_paragraph.py:147 msgid "Modify shared paragraph data" msgstr "" #: apps/shared/views/shared_paragraph.py:174 #: apps/shared/views/shared_paragraph.py:175 #: apps/shared/views/shared_paragraph.py:176 msgid "Get shared paragraph details" msgstr "" #: apps/shared/views/shared_paragraph.py:196 #: apps/shared/views/shared_paragraph.py:197 #: apps/shared/views/shared_paragraph.py:198 msgid "Delete shared paragraph" msgstr "" #: apps/shared/views/shared_paragraph.py:227 #: apps/shared/views/shared_paragraph.py:228 #: apps/shared/views/shared_paragraph.py:229 msgid "Add shared associated questions" msgstr "" #: apps/shared/views/shared_paragraph.py:254 #: apps/shared/views/shared_paragraph.py:255 #: apps/shared/views/shared_paragraph.py:256 msgid "Get a list of shared paragraph questions" msgstr "" #: apps/shared/views/shared_paragraph.py:277 #: apps/shared/views/shared_paragraph.py:278 #: apps/shared/views/shared_paragraph.py:279 msgid "Disassociation shared issue" msgstr "" #: apps/shared/views/shared_paragraph.py:308 #: apps/shared/views/shared_paragraph.py:309 #: apps/shared/views/shared_paragraph.py:310 msgid "Related shared questions" msgstr "" #: apps/shared/views/shared_paragraph.py:340 #: apps/shared/views/shared_paragraph.py:341 #: apps/shared/views/shared_paragraph.py:342 msgid "Get shared paragraph list by pagination" msgstr "" #: apps/shared/views/shared_problem.py:23 #: apps/shared/views/shared_problem.py:24 #: apps/shared/views/shared_problem.py:25 msgid "Shared question list" msgstr "" #: apps/shared/views/shared_problem.py:28 #: apps/shared/views/shared_problem.py:50 #: apps/shared/views/shared_problem.py:71 #: apps/shared/views/shared_problem.py:94 #: apps/shared/views/shared_problem.py:115 #: apps/shared/views/shared_problem.py:135 #: apps/shared/views/shared_problem.py:158 #: apps/shared/views/shared_problem.py:182 msgid "Shared Knowledge/Documentation/Paragraph/Question" msgstr "" #: apps/shared/views/shared_problem.py:44 #: apps/shared/views/shared_problem.py:45 #: apps/shared/views/shared_problem.py:46 msgid "Create shared question" msgstr "" #: apps/shared/views/shared_problem.py:66 #: apps/shared/views/shared_problem.py:67 #: apps/shared/views/shared_problem.py:68 msgid "Get a list of associated shared paragraphs" msgstr "" #: apps/shared/views/shared_problem.py:88 #: apps/shared/views/shared_problem.py:89 #: apps/shared/views/shared_problem.py:90 msgid "Batch associated shared paragraphs" msgstr "" #: apps/shared/views/shared_problem.py:109 #: apps/shared/views/shared_problem.py:110 #: apps/shared/views/shared_problem.py:111 msgid "Batch deletion shared issues" msgstr "" #: apps/shared/views/shared_problem.py:130 #: apps/shared/views/shared_problem.py:131 #: apps/shared/views/shared_problem.py:132 msgid "Delete shared question" msgstr "" #: apps/shared/views/shared_problem.py:152 #: apps/shared/views/shared_problem.py:153 #: apps/shared/views/shared_problem.py:154 msgid "Modify shared question" msgstr "" #: apps/shared/views/shared_problem.py:177 #: apps/shared/views/shared_problem.py:178 #: apps/shared/views/shared_problem.py:179 msgid "Get the list of shared questions by page" msgstr "" #: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26 #: apps/shared/views/shared_tool.py:27 msgid "Get share resource tool" msgstr "" #: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44 #: apps/shared/views/shared_tool.py:45 msgid "Create shared tool" msgstr "" #: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63 #: apps/shared/views/shared_tool.py:64 msgid "Update shared tool" msgstr "" #: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79 #: apps/shared/views/shared_tool.py:80 msgid "Get shared tool" msgstr "" #: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94 #: apps/shared/views/shared_tool.py:95 msgid "Delete shared tool" msgstr "" #: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112 #: apps/shared/views/shared_tool.py:113 msgid "Get shared tool list by pagination" msgstr "" #: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135 #: apps/shared/views/shared_tool.py:136 msgid "Import shared tool" msgstr "" #: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153 #: apps/shared/views/shared_tool.py:154 msgid "Export shared tool" msgstr "" #: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171 #: apps/shared/views/shared_tool.py:172 msgid "Debug shared Tool" msgstr "" #: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189 #: apps/shared/views/shared_tool.py:190 msgid "Check shared code" msgstr "" #: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213 #: apps/shared/views/shared_tool.py:214 msgid "Edit shared tool icon" msgstr "" #: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235 msgid "Authorization tool workspace" msgstr "" #: apps/shared/views/shared_tool.py:234 msgid "Authorization tool workspace " msgstr "" #: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250 #: apps/shared/views/shared_tool.py:251 msgid "Get Authorization tool workspace" msgstr "" #: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269 #: apps/shared/views/shared_tool.py:270 msgid "Get shared workspace tool" msgstr "" #: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290 #: apps/shared/views/shared_tool.py:291 msgid "Get shared workspace tool list by pagination" msgstr "" #: apps/system_manage/serializers/email_setting.py:28 msgid "SMTP host" msgstr "" #: apps/system_manage/serializers/email_setting.py:29 msgid "SMTP port" msgstr "" #: apps/system_manage/serializers/email_setting.py:30 #: apps/system_manage/serializers/email_setting.py:34 msgid "Sender's email" msgstr "" #: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93 #: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57 #: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291 #: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25 #: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24 #: apps/xpack/serializers/chat_user.py:74 #: apps/xpack/serializers/chat_user.py:267 #: apps/xpack/serializers/chat_user.py:601 msgid "Password" msgstr "" #: apps/system_manage/serializers/email_setting.py:32 msgid "Whether to enable TLS" msgstr "" #: apps/system_manage/serializers/email_setting.py:33 msgid "Whether to enable SSL" msgstr "" #: apps/system_manage/serializers/email_setting.py:49 msgid "Email verification failed" msgstr "" #: apps/system_manage/serializers/user_resource_permission.py:53 msgid "target id" msgstr "" #: apps/system_manage/serializers/user_resource_permission.py:70 msgid "Non-existent application|knowledge base id[" msgstr "" #: apps/system_manage/views/email_setting.py:50 #: apps/system_manage/views/email_setting.py:51 #: apps/system_manage/views/email_setting.py:52 msgid "Create or update email settings" msgstr "" #: apps/system_manage/views/email_setting.py:55 #: apps/system_manage/views/email_setting.py:70 #: apps/system_manage/views/email_setting.py:86 msgid "Email Settings" msgstr "" #: apps/system_manage/views/email_setting.py:66 #: apps/system_manage/views/email_setting.py:67 msgid "Test email settings" msgstr "" #: apps/system_manage/views/email_setting.py:82 #: apps/system_manage/views/email_setting.py:83 #: apps/system_manage/views/email_setting.py:84 msgid "Get email settings" msgstr "" #: apps/system_manage/views/system_profile.py:22 #: apps/system_manage/views/system_profile.py:23 msgid "Get MaxKB related information" msgstr "" #: apps/system_manage/views/system_profile.py:25 msgid "System parameters" msgstr "" #: apps/system_manage/views/user_resource_permission.py:40 #: apps/system_manage/views/user_resource_permission.py:41 msgid "Obtain resource authorization list" msgstr "" #: apps/system_manage/views/user_resource_permission.py:44 #: apps/system_manage/views/user_resource_permission.py:60 msgid "Resources authorization" msgstr "" #: apps/system_manage/views/user_resource_permission.py:55 #: apps/system_manage/views/user_resource_permission.py:56 msgid "Modify the resource authorization list" msgstr "" #: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169 msgid "variable name" msgstr "" #: apps/tools/serializers/tool.py:122 msgid "fields only support string|int|dict|array|float" msgstr "" #: apps/tools/serializers/tool.py:131 msgid "field name" msgstr "" #: apps/tools/serializers/tool.py:132 msgid "field label" msgstr "" #: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160 #: apps/tools/serializers/tool.py:174 msgid "tool content" msgstr "" #: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161 #: apps/tools/serializers/tool.py:175 msgid "input field list" msgstr "" #: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162 #: apps/tools/serializers/tool.py:176 msgid "init field list" msgstr "" #: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177 msgid "init params" msgstr "" #: apps/tools/serializers/tool.py:170 msgid "variable value" msgstr "" #: apps/tools/serializers/tool.py:182 msgid "function content" msgstr "" #: apps/tools/serializers/tool.py:238 msgid "field has no value set" msgstr "" #: apps/tools/serializers/tool.py:262 #, python-brace-format msgid "Field: {name} Type: {_type} Value: {value} Type conversion error" msgstr "" #: apps/tools/serializers/tool.py:275 msgid "Tool not found" msgstr "" #: apps/tools/serializers/tool.py:388 msgid "function ID" msgstr "" #: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34 #: apps/tools/views/tool.py:35 msgid "Create tool" msgstr "" #: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57 #: apps/tools/views/tool.py:58 msgid "Get tool by folder" msgstr "" #: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78 #: apps/tools/views/tool.py:79 msgid "Debug Tool" msgstr "" #: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99 #: apps/tools/views/tool.py:100 msgid "Update tool" msgstr "" #: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123 #: apps/tools/views/tool.py:124 msgid "Get tool" msgstr "" #: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142 #: apps/tools/views/tool.py:143 msgid "Delete tool" msgstr "" #: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168 #: apps/tools/views/tool.py:169 msgid "Get tool list by pagination" msgstr "" #: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196 #: apps/tools/views/tool.py:197 msgid "Import tool" msgstr "" #: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219 #: apps/tools/views/tool.py:220 msgid "Export tool" msgstr "" #: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245 #: apps/tools/views/tool.py:246 msgid "Check code" msgstr "" #: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269 #: apps/tools/views/tool.py:270 msgid "Edit tool icon" msgstr "" #: apps/users/api/user.py:154 msgid "Email or Username" msgstr "" #: apps/users/api/user.py:224 msgid "Language" msgstr "" #: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69 msgid "captcha" msgstr "" #: apps/users/serializers/login.py:50 #: apps/xpack/serializers/chat_user_serializer.py:120 msgid "Captcha code error or expiration" msgstr "" #: apps/users/serializers/login.py:55 #: apps/xpack/serializers/auth_config_serializer.py:192 #: apps/xpack/serializers/chat_user_serializer.py:125 #: apps/xpack/serializers/qr_login/qr_login.py:37 msgid "The user has been disabled, please contact the administrator!" msgstr "" #: apps/users/serializers/user.py:47 msgid "Is Edit Password" msgstr "" #: apps/users/serializers/user.py:48 msgid "permissions" msgstr "" #: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106 #: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557 #: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107 #: apps/xpack/serializers/chat_user.py:54 #: apps/xpack/serializers/chat_user.py:227 msgid "Email" msgstr "" #: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140 #: apps/xpack/serializers/chat_user.py:88 msgid "Nick name" msgstr "" #: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145 #: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108 #: apps/xpack/serializers/chat_user.py:93 #: apps/xpack/serializers/chat_user.py:240 msgid "Phone" msgstr "" #: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68 msgid "Username must be 4-64 characters long" msgstr "" #: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298 #: apps/xpack/serializers/chat_user.py:81 #: apps/xpack/serializers/chat_user.py:274 msgid "" "The password must be 6-20 characters long and must be a combination of " "letters, numbers, and special characters." msgstr "" #: apps/users/serializers/user.py:170 msgid "Email or username" msgstr "" #: apps/users/serializers/user.py:226 msgid "" "The community version supports up to 2 users. If you need more users, please " "contact us (https://fit2cloud.com/)." msgstr "" #: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31 #: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247 msgid "Is Active" msgstr "" #: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262 msgid "Nickname is already in use" msgstr "" #: apps/users/serializers/user.py:286 msgid "Email is already in use" msgstr "" #: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281 msgid "Re Password" msgstr "" #: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522 #: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286 #: apps/xpack/serializers/chat_user.py:606 #: apps/xpack/serializers/chat_user.py:613 msgid "" "The confirmation password must be 6-20 characters long and must be a " "combination of letters, numbers, and special characters." msgstr "" #: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309 msgid "User does not exist" msgstr "" #: apps/users/serializers/user.py:348 msgid "Unable to delete administrator" msgstr "" #: apps/users/serializers/user.py:366 msgid "Cannot modify administrator status" msgstr "" #: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164 #: apps/xpack/serializers/chat_user.py:192 #: apps/xpack/serializers/chat_user.py:512 msgid "User IDs cannot be empty" msgstr "" #: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608 msgid "Confirm Password" msgstr "" #: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647 #: apps/xpack/api/knowledge_lark.py:26 msgid "Type" msgstr "" #: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651 msgid "The type only supports register|reset_password" msgstr "" #: apps/users/serializers/user.py:581 #, python-brace-format msgid "Do not send emails again within {seconds} seconds" msgstr "" #: apps/users/serializers/user.py:611 msgid "" "The email service has not been set up. Please contact the administrator to " "set up the email service in [Email Settings]." msgstr "" #: apps/users/serializers/user.py:622 #, python-brace-format msgid "【Intelligent knowledge base question and answer system-{action}】" msgstr "" #: apps/users/serializers/user.py:623 msgid "User registration" msgstr "" #: apps/users/serializers/user.py:623 apps/users/views/user.py:248 #: apps/users/views/user.py:249 apps/users/views/user.py:250 #: apps/users/views/user.py:283 apps/users/views/user.py:284 #: apps/users/views/user.py:285 msgid "Change password" msgstr "" #: apps/users/serializers/user.py:644 msgid "Verification code" msgstr "" #: apps/users/serializers/user.py:672 msgid "language only support:" msgstr "" #: apps/users/views/login.py:38 apps/users/views/login.py:39 #: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184 #: apps/xpack/views/chat_user_auth.py:185 #: apps/xpack/views/chat_user_auth.py:186 msgid "Log in" msgstr "" #: apps/users/views/login.py:55 apps/users/views/login.py:56 #: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203 #: apps/xpack/views/chat_user_auth.py:204 #: apps/xpack/views/chat_user_auth.py:205 msgid "Sign out" msgstr "" #: apps/users/views/user.py:60 apps/users/views/user.py:61 #: apps/users/views/user.py:62 apps/users/views/user.py:74 #: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359 #: apps/xpack/views/system_chat_user.py:360 #: apps/xpack/views/system_chat_user.py:361 msgid "Get current user information" msgstr "" #: apps/users/views/user.py:120 apps/users/views/user.py:121 #: apps/users/views/user.py:122 msgid "Get all user" msgstr "" #: apps/users/views/user.py:133 apps/users/views/user.py:134 #: apps/users/views/user.py:135 msgid "Get user list under workspace" msgstr "" #: apps/users/views/user.py:147 apps/users/views/user.py:148 #: apps/users/views/user.py:149 msgid "Get user member under workspace" msgstr "" #: apps/users/views/user.py:161 apps/users/views/user.py:162 #: apps/users/views/user.py:163 msgid "Create user" msgstr "" #: apps/users/views/user.py:177 apps/users/views/user.py:178 #: apps/users/views/user.py:179 msgid "Get default password" msgstr "" #: apps/users/views/user.py:190 apps/users/views/user.py:191 #: apps/users/views/user.py:192 msgid "Delete user" msgstr "" #: apps/users/views/user.py:203 apps/users/views/user.py:204 #: apps/users/views/user.py:205 msgid "Get user information" msgstr "" #: apps/users/views/user.py:214 apps/users/views/user.py:215 #: apps/users/views/user.py:216 msgid "Update user information" msgstr "" #: apps/users/views/user.py:232 apps/users/views/user.py:233 #: apps/users/views/user.py:234 msgid "Batch delete user" msgstr "" #: apps/users/views/user.py:266 apps/users/views/user.py:267 #: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168 #: apps/workspace/views/workspace_chat_user.py:169 #: apps/workspace/views/workspace_chat_user.py:170 #: apps/xpack/views/system_chat_user.py:197 #: apps/xpack/views/system_chat_user.py:198 #: apps/xpack/views/system_chat_user.py:199 msgid "Get user paginated list" msgstr "" #: apps/users/views/user.py:300 apps/users/views/user.py:301 #: apps/users/views/user.py:302 msgid "Send email" msgstr "" #: apps/users/views/user.py:318 apps/users/views/user.py:319 #: apps/users/views/user.py:320 msgid "Check whether the verification code is correct" msgstr "" #: apps/users/views/user.py:335 apps/users/views/user.py:336 #: apps/users/views/user.py:337 msgid "Send email to current user" msgstr "" #: apps/users/views/user.py:353 apps/users/views/user.py:354 #: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336 #: apps/xpack/views/system_chat_user.py:337 #: apps/xpack/views/system_chat_user.py:338 msgid "Modify current user password" msgstr "" #: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352 msgid "Failed to change password" msgstr "" #: apps/workspace/api/workspace.py:73 #: apps/workspace/serializers/workspace_serializers.py:213 msgid "User Relation ID" msgstr "" #: apps/workspace/api/workspace.py:87 msgid "Role Name" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:42 #: apps/workspace/serializers/workspace_serializers.py:95 #: apps/workspace/serializers/workspace_serializers.py:110 #: apps/workspace/serializers/workspace_serializers.py:177 #: apps/workspace/serializers/workspace_serializers.py:219 #: apps/workspace/serializers/workspace_serializers.py:246 msgid "Workspace does not exist" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:49 msgid "Workspace name already exists" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:97 #: apps/workspace/serializers/workspace_serializers.py:112 msgid "Default workspace cannot be deleted" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:122 msgid "Applications Resource" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:124 msgid "Knowledge Resource" msgstr "" #: apps/workspace/serializers/workspace_serializers.py:130 #, python-format msgid "This workspace contains %s, cannot be deleted." msgstr "" #: apps/workspace/serializers/workspace_serializers.py:166 msgid "Role IDs" msgstr "" #: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33 #: apps/workspace/views/workspace.py:34 msgid "Create or update workspace" msgstr "" #: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46 #: apps/workspace/views/workspace.py:47 msgid "Get system workspace list" msgstr "" #: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59 #: apps/workspace/views/workspace.py:60 msgid "Delete workspace" msgstr "" #: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76 #: apps/workspace/views/workspace.py:77 msgid "Check workspace can it be deleted" msgstr "" #: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96 #: apps/workspace/views/workspace.py:97 msgid "Add member to system workspace" msgstr "" #: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115 #: apps/workspace/views/workspace.py:116 msgid "Remove member from system workspace" msgstr "" #: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134 #: apps/workspace/views/workspace.py:135 msgid "Get system workspace member list" msgstr "" #: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152 #: apps/workspace/views/workspace.py:153 msgid "Get workspace list" msgstr "" #: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165 #: apps/workspace/views/workspace.py:166 msgid "Add member to workspace" msgstr "" #: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184 #: apps/workspace/views/workspace.py:185 msgid "Remove member from workspace" msgstr "" #: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203 #: apps/workspace/views/workspace.py:204 msgid "Get workspace member list" msgstr "" #: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220 #: apps/workspace/views/workspace.py:221 msgid "Get workspace list by current user" msgstr "" #: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233 #: apps/workspace/views/workspace.py:234 msgid "Get workspace list by user" msgstr "" #: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247 #: apps/workspace/views/workspace.py:248 msgid "Get current user role list" msgstr "" #: apps/workspace/views/workspace_chat_user.py:44 #: apps/workspace/views/workspace_chat_user.py:45 #: apps/workspace/views/workspace_chat_user.py:46 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/xpack/views/system_chat_user.py:57 #: apps/xpack/views/system_chat_user.py:58 #: apps/xpack/views/system_chat_user.py:59 msgid "Create chat user" msgstr "" #: apps/workspace/views/workspace_chat_user.py:47 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/workspace/views/workspace_chat_user.py:63 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/workspace/views/workspace_chat_user.py:76 #: apps/workspace/views/workspace_chat_user.py:87 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/workspace/views/workspace_chat_user.py:105 #: apps/workspace/views/workspace_chat_user.py:120 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/workspace/views/workspace_chat_user.py:136 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/workspace/views/workspace_chat_user.py:152 #: apps/workspace/views/workspace_chat_user.py:171 msgid "Workspace/Chat user" msgstr "" #: apps/workspace/views/workspace_chat_user.py:60 #: apps/workspace/views/workspace_chat_user.py:61 #: apps/workspace/views/workspace_chat_user.py:62 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/xpack/views/system_chat_user.py:86 #: apps/xpack/views/system_chat_user.py:87 #: apps/xpack/views/system_chat_user.py:88 msgid "Delete chat user" msgstr "" #: apps/workspace/views/workspace_chat_user.py:73 #: apps/workspace/views/workspace_chat_user.py:74 #: apps/workspace/views/workspace_chat_user.py:75 #: apps/xpack/views/system_chat_user.py:99 #: apps/xpack/views/system_chat_user.py:100 #: apps/xpack/views/system_chat_user.py:101 msgid "Get chat user information" msgstr "" #: apps/workspace/views/workspace_chat_user.py:84 #: apps/workspace/views/workspace_chat_user.py:85 #: apps/workspace/views/workspace_chat_user.py:86 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/xpack/views/system_chat_user.py:110 #: apps/xpack/views/system_chat_user.py:111 #: apps/xpack/views/system_chat_user.py:112 msgid "Update chat user information" msgstr "" #: apps/workspace/views/workspace_chat_user.py:102 #: apps/workspace/views/workspace_chat_user.py:103 #: apps/workspace/views/workspace_chat_user.py:104 #: apps/workspace/views/workspace_chat_user.py:261 #: apps/workspace/views/workspace_chat_user.py:262 #: apps/workspace/views/workspace_chat_user.py:263 #: apps/xpack/views/system_chat_user.py:128 #: apps/xpack/views/system_chat_user.py:129 #: apps/xpack/views/system_chat_user.py:130 #: apps/xpack/views/system_chat_user.py:318 #: apps/xpack/views/system_chat_user.py:319 #: apps/xpack/views/system_chat_user.py:320 msgid "Get user list by group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:117 #: apps/workspace/views/workspace_chat_user.py:118 #: apps/workspace/views/workspace_chat_user.py:119 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/xpack/views/system_chat_user.py:143 #: apps/xpack/views/system_chat_user.py:144 #: apps/xpack/views/system_chat_user.py:145 msgid "Batch delete chat user" msgstr "" #: apps/workspace/views/workspace_chat_user.py:133 #: apps/workspace/views/workspace_chat_user.py:134 #: apps/workspace/views/workspace_chat_user.py:135 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/xpack/views/system_chat_user.py:160 #: apps/xpack/views/system_chat_user.py:161 #: apps/xpack/views/system_chat_user.py:162 msgid "Batch add chat user to group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:149 #: apps/workspace/views/workspace_chat_user.py:150 #: apps/workspace/views/workspace_chat_user.py:151 #: apps/xpack/views/system_chat_user.py:177 #: apps/xpack/views/system_chat_user.py:178 #: apps/xpack/views/system_chat_user.py:179 msgid "Change chat user password" msgstr "" #: apps/workspace/views/workspace_chat_user.py:186 #: apps/workspace/views/workspace_chat_user.py:187 #: apps/workspace/views/workspace_chat_user.py:188 #: apps/xpack/views/system_chat_user.py:230 #: apps/xpack/views/system_chat_user.py:231 #: apps/xpack/views/system_chat_user.py:232 msgid "Create or update Chat User Group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:191 #: apps/workspace/views/workspace_chat_user.py:202 #: apps/workspace/views/workspace_chat_user.py:216 #: apps/workspace/views/workspace_chat_user.py:232 #: apps/workspace/views/workspace_chat_user.py:249 #: apps/workspace/views/workspace_chat_user.py:264 msgid "Workspace/User Group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:198 #: apps/workspace/views/workspace_chat_user.py:199 #: apps/workspace/views/workspace_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:243 #: apps/xpack/views/system_chat_user.py:244 #: apps/xpack/views/system_chat_user.py:245 msgid "Get user group list" msgstr "" #: apps/workspace/views/workspace_chat_user.py:211 #: apps/workspace/views/workspace_chat_user.py:212 #: apps/workspace/views/workspace_chat_user.py:213 #: apps/xpack/views/system_chat_user.py:256 #: apps/xpack/views/system_chat_user.py:257 #: apps/xpack/views/system_chat_user.py:258 msgid "Delete chat user group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:226 #: apps/workspace/views/workspace_chat_user.py:227 #: apps/workspace/views/workspace_chat_user.py:228 #: apps/xpack/views/system_chat_user.py:273 #: apps/xpack/views/system_chat_user.py:274 #: apps/xpack/views/system_chat_user.py:275 msgid "Add member to chat user group" msgstr "" #: apps/workspace/views/workspace_chat_user.py:243 #: apps/workspace/views/workspace_chat_user.py:244 #: apps/workspace/views/workspace_chat_user.py:245 #: apps/xpack/views/system_chat_user.py:295 #: apps/xpack/views/system_chat_user.py:296 #: apps/xpack/views/system_chat_user.py:297 msgid "Remove member from chat user group" msgstr "" #: apps/xpack/api/auth_config.py:29 msgid "Auth Type" msgstr "" #: apps/xpack/api/auth_config.py:30 msgid "Config" msgstr "" #: apps/xpack/api/auth_config.py:77 msgid "Corp ID" msgstr "" #: apps/xpack/api/auth_config.py:78 msgid "Agent ID" msgstr "" #: apps/xpack/api/auth_config.py:79 msgid "App Secret" msgstr "" #: apps/xpack/api/auth_config.py:80 msgid "Callback URL" msgstr "" #: apps/xpack/api/auth_config.py:84 msgid "Key" msgstr "" #: apps/xpack/api/auth_config.py:106 msgid "Access Token" msgstr "" #: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83 #: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101 #: apps/xpack/serializers/chat_user.py:177 #: apps/xpack/serializers/chat_user.py:252 msgid "User Group IDs" msgstr "" #: apps/xpack/api/chat_user.py:118 msgid "User Group Names" msgstr "" #: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120 #: apps/xpack/serializers/resource_chat_user.py:37 msgid "Username or Nickname" msgstr "" #: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360 msgid "Sync Type" msgstr "" #: apps/xpack/api/knowledge_lark.py:25 msgid "Token" msgstr "" #: apps/xpack/api/knowledge_lark.py:27 msgid "Is Exist" msgstr "" #: apps/xpack/api/license.py:13 msgid "corporation" msgstr "" #: apps/xpack/api/license.py:14 msgid "isv" msgstr "" #: apps/xpack/api/license.py:15 msgid "expired" msgstr "" #: apps/xpack/api/license.py:16 msgid "product" msgstr "" #: apps/xpack/api/license.py:17 msgid "edition" msgstr "" #: apps/xpack/api/license.py:18 msgid "license version" msgstr "" #: apps/xpack/api/license.py:19 msgid "count" msgstr "" #: apps/xpack/api/license.py:20 msgid "serial number" msgstr "" #: apps/xpack/api/license.py:21 msgid "remark" msgstr "" #: apps/xpack/api/license.py:26 msgid "message" msgstr "" #: apps/xpack/api/license.py:27 msgid "license details" msgstr "" #: apps/xpack/api/license.py:36 #: apps/xpack/serializers/license/license_serializers.py:56 msgid "license file" msgstr "" #: apps/xpack/api/license.py:37 msgid "License file is required" msgstr "" #: apps/xpack/api/license.py:38 msgid "Invalid license file format" msgstr "" #: apps/xpack/api/operate_log.py:12 #: apps/xpack/serializers/operate_log_serializer.py:57 msgid "menu" msgstr "" #: apps/xpack/api/operate_log.py:13 #: apps/xpack/serializers/operate_log_serializer.py:58 msgid "operate" msgstr "" #: apps/xpack/api/operate_log.py:14 msgid "menu_label" msgstr "" #: apps/xpack/api/operate_log.py:15 msgid "operate_label" msgstr "" #: apps/xpack/api/platform.py:35 msgid "Platform type" msgstr "" #: apps/xpack/api/platform.py:50 msgid "Platform configuration" msgstr "" #: apps/xpack/api/resource_chat_user_group.py:40 #: apps/xpack/serializers/resource_chat_user.py:25 #: apps/xpack/serializers/resource_chat_user_group.py:69 msgid "is auth" msgstr "" #: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99 msgid "User Group ID" msgstr "" #: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406 #: apps/xpack/serializers/chat_user.py:563 msgid "Group ID" msgstr "" #: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541 msgid "User group relation IDs" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:19 msgid "theme color" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:21 msgid "header font color" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:25 msgid "float location type" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:26 msgid "float location value" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:30 msgid "float location x" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:31 msgid "float location y" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:35 msgid "show source" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:36 msgid "show exec" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:38 msgid "show history" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:39 msgid "draggable" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:40 msgid "show guide" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:42 msgid "icon url" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:43 msgid "chat background" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:44 msgid "chat background url" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:45 msgid "avatar" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:46 msgid "avatar url" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:47 msgid "user avatar" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:48 msgid "user avatar url" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:49 msgid "float icon" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:50 msgid "float icon url" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:51 msgid "disclaimer" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:52 msgid "disclaimer value" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:55 msgid "show avatar" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:56 msgid "show user avatar" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:124 msgid "Float location field type error" msgstr "" #: apps/xpack/serializers/application_setting_serializer.py:130 msgid "Custom theme field type error" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:27 #: apps/xpack/serializers/platform_serializer.py:31 msgid "App Secret is required" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:28 #: apps/xpack/serializers/platform_serializer.py:26 #: apps/xpack/serializers/platform_serializer.py:34 #: apps/xpack/serializers/platform_serializer.py:40 #: apps/xpack/serializers/platform_serializer.py:46 msgid "Callback URL is required" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:32 #: apps/xpack/serializers/auth_config_serializer.py:41 msgid "Corp ID is required" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:33 #: apps/xpack/serializers/platform_serializer.py:22 msgid "Agent ID is required" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:37 #: apps/xpack/serializers/auth_config_serializer.py:42 msgid "App Key is required" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:53 msgid "LDAP server cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:54 msgid "Base DN cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:55 msgid "Password cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:56 msgid "OU cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:57 msgid "LDAP filter cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:58 msgid "LDAP mapping cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:62 msgid "Authorization address cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:63 msgid "Token address cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:64 msgid "User information address cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:65 msgid "Scope cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:66 msgid "Client ID cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:67 msgid "Client secret cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:68 msgid "Redirect address cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:69 msgid "Field mapping cannot be empty" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:262 msgid "Configuration information is wrong and failed to save" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:288 msgid "Connection failed" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:306 msgid "Platform does not exist" msgstr "" #: apps/xpack/serializers/auth_config_serializer.py:316 msgid "Unsupported platform type" msgstr "" #: apps/xpack/serializers/channel/chat_manage.py:100 msgid "Think: " msgstr "" #: apps/xpack/serializers/channel/chat_manage.py:103 #: apps/xpack/serializers/channel/chat_manage.py:105 msgid "AI reply: " msgstr "" #: apps/xpack/serializers/channel/chat_manage.py:318 msgid "Thinking, please wait a moment!" msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:19 #: apps/xpack/serializers/channel/wechat.py:91 #: apps/xpack/serializers/channel/wechat.py:132 #: apps/xpack/serializers/channel/wecom.py:78 #: apps/xpack/serializers/channel/wecom.py:259 msgid "The corresponding platform configuration was not found" msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:27 #: apps/xpack/serializers/channel/lark.py:117 msgid "Currently only text messages are supported" msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:91 #: apps/xpack/serializers/channel/wechat.py:163 #: apps/xpack/serializers/channel/wecom.py:189 msgid "Image download failed, check network" msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:92 #: apps/xpack/serializers/channel/wechat.py:161 #: apps/xpack/serializers/channel/wecom.py:185 msgid "Please analyze the content of the image." msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:95 #, python-brace-format msgid "DingTalk application: {user}" msgstr "" #: apps/xpack/serializers/channel/ding_talk.py:106 #: apps/xpack/serializers/channel/ding_talk.py:151 msgid "Content generated by AI" msgstr "" #: apps/xpack/serializers/channel/lark.py:92 msgid "Lark application: " msgstr "" #: apps/xpack/serializers/channel/slack.py:116 msgid "The corresponding platform configuration for Slack was not found" msgstr "" #: apps/xpack/serializers/channel/slack.py:206 msgid "Thinking..." msgstr "" #: apps/xpack/serializers/channel/slack.py:333 msgid "Invalid json format." msgstr "" #: apps/xpack/serializers/channel/slack.py:339 msgid "Invalid Slack request" msgstr "" #: apps/xpack/serializers/channel/slack.py:347 #, python-brace-format msgid "Slack application: {user}" msgstr "" #: apps/xpack/serializers/channel/slack.py:480 msgid "Stop" msgstr "" #: apps/xpack/serializers/channel/tools.py:58 #, python-brace-format msgid "" "Thinking about 【{question}】...If you want me to continue answering, please " "reply {trigger_message}" msgstr "" #: apps/xpack/serializers/channel/tools.py:158 msgid "" "\n" " ------------\n" "[To be continued, reply \"Continue to answer the question]" msgstr "" #: apps/xpack/serializers/channel/tools.py:238 #, python-brace-format msgid "" "To be continued, reply \"{trigger_message}\" to continue answering the " "question" msgstr "" #: apps/xpack/serializers/channel/wechat.py:143 #, python-brace-format msgid "WeChat Official Account: {account}" msgstr "" #: apps/xpack/serializers/channel/wechat.py:150 #: apps/xpack/serializers/channel/wecom.py:171 #: apps/xpack/serializers/channel/wecom.py:175 msgid "" "The app does not enable the speech-to-text function or the speech-to-text " "function fails." msgstr "" #: apps/xpack/serializers/channel/wechat.py:189 msgid "Message types not supported yet" msgstr "" #: apps/xpack/serializers/channel/wechat.py:196 msgid "Welcome to subscribe" msgstr "" #: apps/xpack/serializers/channel/wecom.py:86 msgid "Enterprise WeChat user: " msgstr "" #: apps/xpack/serializers/channel/wecom.py:97 msgid "Enterprise WeChat customer service: " msgstr "" #: apps/xpack/serializers/channel/wecom.py:134 #: apps/xpack/serializers/channel/wecom.py:150 msgid "This type of message is not supported yet" msgstr "" #: apps/xpack/serializers/channel/wecom.py:254 msgid "Signature missing" msgstr "" #: apps/xpack/serializers/channel/wecom.py:266 #: apps/xpack/serializers/channel/wecom.py:273 #, python-brace-format msgid "An error occurred while processing the GET request {e}" msgstr "" #: apps/xpack/serializers/chat_auth.py:51 msgid "The password is incorrect" msgstr "" #: apps/xpack/serializers/chat_user.py:42 msgid "Some user groups do not exist" msgstr "" #: apps/xpack/serializers/chat_user.py:181 msgid "Is Append" msgstr "" #: apps/xpack/serializers/chat_user.py:194 msgid "User Group IDs cannot be empty" msgstr "" #: apps/xpack/serializers/chat_user.py:198 msgid "Some users do not exist" msgstr "" #: apps/xpack/serializers/chat_user.py:361 msgid "Sync Type: LOCAL or LDAP" msgstr "" #: apps/xpack/serializers/chat_user.py:403 msgid "Unsupported sync type" msgstr "" #: apps/xpack/serializers/chat_user.py:412 #: apps/xpack/serializers/chat_user.py:444 #: apps/xpack/serializers/chat_user.py:483 #: apps/xpack/serializers/chat_user.py:510 #: apps/xpack/serializers/chat_user.py:548 #: apps/xpack/serializers/chat_user.py:570 msgid "User group does not exist" msgstr "" #: apps/xpack/serializers/chat_user.py:451 msgid "User group name already exists" msgstr "" #: apps/xpack/serializers/chat_user.py:485 msgid "Default user group cannot be deleted" msgstr "" #: apps/xpack/serializers/chat_user.py:550 msgid "User group relation IDs cannot be empty" msgstr "" #: apps/xpack/serializers/chat_user_serializer.py:75 msgid "Invalid access token" msgstr "" #: apps/xpack/serializers/chat_user_serializer.py:102 msgid "The user does not have permission to access the application" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:56 #: apps/xpack/serializers/dataset_lark_serializer.py:299 msgid "app id" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:57 #: apps/xpack/serializers/dataset_lark_serializer.py:300 msgid "app secret" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:58 #: apps/xpack/serializers/dataset_lark_serializer.py:105 #: apps/xpack/serializers/dataset_lark_serializer.py:301 msgid "folder token" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:60 msgid "embedding model" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:71 #: apps/xpack/serializers/dataset_lark_serializer.py:311 msgid "Network error or folder token error!" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:113 #: apps/xpack/serializers/dataset_lark_serializer.py:155 #: apps/xpack/task/sync.py:308 msgid "Knowledge base not found!" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:125 #: apps/xpack/task/sync.py:240 msgid "Failed to get lark document list!" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:147 msgid "Knowledge id" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:169 msgid "Synchronization is only supported for lark documents" msgstr "" #: apps/xpack/serializers/license/license_serializers.py:102 #: apps/xpack/serializers/license/license_serializers.py:123 #: apps/xpack/serializers/license/license_tools.py:111 msgid "The license is invalid" msgstr "" #: apps/xpack/serializers/license/license_tools.py:136 msgid "License usage limit exceeded." msgstr "" #: apps/xpack/serializers/license/license_tools.py:160 msgid "The network is busy, try again later." msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:59 msgid "user" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:61 msgid "ip_address" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:62 msgid "workspace_id" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Fail" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:171 msgid "Menu" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:172 msgid "Operate" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:173 msgid "Operate user" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:175 msgid "Ip Address" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:176 msgid "API Details" msgstr "" #: apps/xpack/serializers/operate_log_serializer.py:177 msgid "Operate Time" msgstr "" #: apps/xpack/serializers/platform_serializer.py:12 msgid "app_id is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:13 msgid "app_secret is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:14 msgid "token is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:15 msgid "callback_url is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:21 #: apps/xpack/serializers/platform_serializer.py:30 msgid "App ID is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:23 msgid "Secret is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:24 msgid "Token is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:33 msgid "Verification Token is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:38 msgid "Client ID is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:39 msgid "Client Secret is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:44 msgid "Signing Secret is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:45 msgid "Bot User Token is required" msgstr "" #: apps/xpack/serializers/platform_serializer.py:66 msgid "Check if the fields are correct" msgstr "" #: apps/xpack/serializers/platform_serializer.py:155 #, python-brace-format msgid "The platform configuration corresponding to {type} was not found" msgstr "" #: apps/xpack/serializers/resource_chat_user.py:35 #: apps/xpack/serializers/resource_chat_user.py:111 #: apps/xpack/serializers/resource_chat_user_group.py:18 #: apps/xpack/serializers/resource_chat_user_group.py:86 msgid "Resource id" msgstr "" #: apps/xpack/serializers/resource_chat_user.py:38 #: apps/xpack/serializers/resource_chat_user.py:112 msgid "User group id" msgstr "" #: apps/xpack/serializers/resource_chat_user.py:94 msgid "Is auth" msgstr "" #: apps/xpack/serializers/resource_chat_user_group.py:20 msgid "User group name" msgstr "" #: apps/xpack/serializers/resource_chat_user_group.py:68 msgid "user_group_id" msgstr "" #: apps/xpack/serializers/sso_auth/cas.py:32 msgid "HttpClient query failed: " msgstr "" #: apps/xpack/serializers/sso_auth/cas.py:58 msgid "CAS authentication failed" msgstr "" #: apps/xpack/serializers/sso_auth/oauth2.py:165 #: apps/xpack/serializers/sso_auth/oauth2.py:184 #: apps/xpack/serializers/sso_auth/oauth2.py:187 msgid "Failed to obtain user information" msgstr "" #: apps/xpack/serializers/system_api_key.py:12 msgid "Allow cross domain" msgstr "" #: apps/xpack/serializers/system_api_key.py:13 msgid "Cross domain list" msgstr "" #: apps/xpack/serializers/system_api_key.py:44 msgid "system API key id" msgstr "" #: apps/xpack/serializers/system_params.py:20 msgid "theme" msgstr "" #: apps/xpack/serializers/system_params.py:22 msgid "login logo" msgstr "" #: apps/xpack/serializers/system_params.py:23 msgid "login image" msgstr "" #: apps/xpack/serializers/system_params.py:24 msgid "title" msgstr "" #: apps/xpack/serializers/system_params.py:25 msgid "slogan" msgstr "" #: apps/xpack/serializers/system_params.py:26 #: apps/xpack/serializers/system_params.py:27 msgid "show user manual" msgstr "" #: apps/xpack/serializers/system_params.py:28 msgid "user manual url" msgstr "" #: apps/xpack/serializers/system_params.py:29 msgid "show forum" msgstr "" #: apps/xpack/serializers/system_params.py:30 msgid "forum url" msgstr "" #: apps/xpack/serializers/system_params.py:31 msgid "show project" msgstr "" #: apps/xpack/serializers/system_params.py:32 msgid "project url" msgstr "" #: apps/xpack/views/application_setting.py:24 #: apps/xpack/views/application_setting.py:25 #: apps/xpack/views/application_setting.py:26 msgid "Modify Application Settings" msgstr "Modify Agent Settings" #: apps/xpack/views/application_setting.py:42 #: apps/xpack/views/application_setting.py:43 #: apps/xpack/views/application_setting.py:44 msgid "Get Application Settings" msgstr "Get Agent Settings" #: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53 #: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46 #: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48 msgid "Get authentication types" msgstr "" #: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70 #: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108 #: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235 #: apps/xpack/views/auth.py:249 msgid "Authentication Configuration" msgstr "" #: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68 #: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62 #: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64 msgid "Test LDAP connection" msgstr "" #: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88 #: apps/xpack/views/auth.py:89 msgid "Add or modify authentication configuration" msgstr "" #: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106 #: apps/xpack/views/auth.py:107 msgid "Get authentication configuration" msgstr "" #: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119 #: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112 #: apps/xpack/views/chat_user_auth.py:113 #: apps/xpack/views/chat_user_auth.py:114 msgid "Ldap Log in" msgstr "" #: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138 #: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176 #: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208 #: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286 #: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327 #: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368 msgid "Three-party login" msgstr "" #: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136 #: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129 #: apps/xpack/views/chat_user_auth.py:130 #: apps/xpack/views/chat_user_auth.py:131 msgid "CAS Log in" msgstr "" #: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155 #: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148 #: apps/xpack/views/chat_user_auth.py:149 #: apps/xpack/views/chat_user_auth.py:150 msgid "OIDC Log in" msgstr "" #: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174 #: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167 #: apps/xpack/views/chat_user_auth.py:168 #: apps/xpack/views/chat_user_auth.py:169 msgid "OAuth2 Log in" msgstr "" #: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192 #: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220 #: apps/xpack/views/chat_user_auth.py:221 #: apps/xpack/views/chat_user_auth.py:222 msgid "Scan code login type" msgstr "" #: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206 #: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220 #: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222 #: apps/xpack/views/chat_user_auth.py:234 #: apps/xpack/views/chat_user_auth.py:235 #: apps/xpack/views/chat_user_auth.py:236 #: apps/xpack/views/chat_user_auth.py:249 #: apps/xpack/views/chat_user_auth.py:250 #: apps/xpack/views/chat_user_auth.py:251 msgid "Get platform information" msgstr "" #: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233 #: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261 #: apps/xpack/views/chat_user_auth.py:262 #: apps/xpack/views/chat_user_auth.py:263 msgid "Modify platform information" msgstr "" #: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247 #: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275 #: apps/xpack/views/chat_user_auth.py:276 #: apps/xpack/views/chat_user_auth.py:277 msgid "Test platform connection" msgstr "" #: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264 #: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292 #: apps/xpack/views/chat_user_auth.py:293 #: apps/xpack/views/chat_user_auth.py:294 msgid "DingTalk callback" msgstr "" #: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284 #: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312 #: apps/xpack/views/chat_user_auth.py:313 #: apps/xpack/views/chat_user_auth.py:314 msgid "DingTalk OAuth2 callback" msgstr "" #: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305 #: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333 #: apps/xpack/views/chat_user_auth.py:334 #: apps/xpack/views/chat_user_auth.py:335 msgid "WeCom callback" msgstr "" #: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325 #: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353 #: apps/xpack/views/chat_user_auth.py:354 #: apps/xpack/views/chat_user_auth.py:355 msgid "WeCom OAuth2 callback" msgstr "" #: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346 #: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374 #: apps/xpack/views/chat_user_auth.py:375 #: apps/xpack/views/chat_user_auth.py:376 msgid "Lark callback" msgstr "" #: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366 #: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394 #: apps/xpack/views/chat_user_auth.py:395 #: apps/xpack/views/chat_user_auth.py:396 msgid "Lark OAuth2 callback" msgstr "" #: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65 #: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252 #: apps/xpack/views/chat_user_auth.py:264 #: apps/xpack/views/chat_user_auth.py:278 msgid "Chat User/Authentication Configuration" msgstr "" #: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83 #: apps/xpack/views/chat_user_auth.py:84 msgid "Add or modify Chat/Authentication Configuration" msgstr "" #: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100 #: apps/xpack/views/chat_user_auth.py:101 msgid "Get Authentication Configuration" msgstr "" #: apps/xpack/views/chat_user_auth.py:102 msgid "Chat User/login authentication" msgstr "" #: apps/xpack/views/chat_user_auth.py:115 #: apps/xpack/views/chat_user_auth.py:132 #: apps/xpack/views/chat_user_auth.py:151 #: apps/xpack/views/chat_user_auth.py:170 #: apps/xpack/views/chat_user_auth.py:223 #: apps/xpack/views/chat_user_auth.py:237 #: apps/xpack/views/chat_user_auth.py:295 #: apps/xpack/views/chat_user_auth.py:315 #: apps/xpack/views/chat_user_auth.py:336 #: apps/xpack/views/chat_user_auth.py:356 #: apps/xpack/views/chat_user_auth.py:377 #: apps/xpack/views/chat_user_auth.py:397 msgid "Chat User/Three-party login" msgstr "" #: apps/xpack/views/chat_user_auth.py:187 msgid "Chat User/login" msgstr "" #: apps/xpack/views/chat_user_auth.py:414 #: apps/xpack/views/chat_user_auth.py:415 #: apps/xpack/views/chat_user_auth.py:416 msgid "Application Password Certification" msgstr "Agent Password Certification" #: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32 #: apps/xpack/views/license.py:33 msgid "Get license information" msgstr "" #: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44 msgid "Update license information" msgstr "" #: apps/xpack/views/license.py:43 msgid "Update license information by uploading a new license file" msgstr "" #: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22 #: apps/xpack/views/operate_log.py:23 msgid "Get menu operate log" msgstr "" #: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41 #: apps/xpack/views/operate_log.py:57 msgid "System operate log" msgstr "" #: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37 #: apps/xpack/views/operate_log.py:38 msgid "Get paginated operate log" msgstr "" #: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55 #: apps/xpack/views/operate_log.py:56 msgid "Export operate log" msgstr "" #: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61 #: apps/xpack/views/platform.py:62 msgid "Get platform configuration" msgstr "" #: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79 msgid "Application/application access" msgstr "Agent/agent access" #: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74 #: apps/xpack/views/platform.py:75 msgid "Update platform configuration" msgstr "" #: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95 #: apps/xpack/views/platform.py:96 msgid "Get platform status" msgstr "" #: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118 msgid "Application/Get platform status" msgstr "Agent/Get platform status" #: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114 #: apps/xpack/views/platform.py:115 msgid "Update platform status" msgstr "" #: apps/xpack/views/resource_chat_user.py:27 #: apps/xpack/views/resource_chat_user.py:28 #: apps/xpack/views/resource_chat_user.py:29 msgid "Get Resource chat user List" msgstr "" #: apps/xpack/views/resource_chat_user.py:32 #: apps/xpack/views/resource_chat_user.py:54 #: apps/xpack/views/resource_chat_user.py:77 #: apps/xpack/views/system_chat_user_group.py:24 #: apps/xpack/views/system_chat_user_group.py:45 #: apps/xpack/views/system_chat_user_group.py:67 msgid "Chat user" msgstr "" #: apps/xpack/views/resource_chat_user.py:48 #: apps/xpack/views/resource_chat_user.py:49 #: apps/xpack/views/resource_chat_user.py:50 msgid "Edit Resource chat user List" msgstr "" #: apps/xpack/views/resource_chat_user.py:72 #: apps/xpack/views/resource_chat_user.py:73 #: apps/xpack/views/resource_chat_user.py:74 msgid "Get Resource chat user page List" msgstr "" #: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20 #: apps/xpack/views/system_api_key.py:21 msgid "Create SystemAPIKey" msgstr "" #: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35 #: apps/xpack/views/system_api_key.py:36 msgid "Get SystemAPIKey List" msgstr "" #: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51 #: apps/xpack/views/system_api_key.py:52 msgid "Update SystemAPIKey" msgstr "" #: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67 #: apps/xpack/views/system_api_key.py:68 msgid "Delete SystemAPIKey" msgstr "" #: apps/xpack/views/system_chat_user.py:60 #: apps/xpack/views/system_chat_user.py:76 #: apps/xpack/views/system_chat_user.py:89 #: apps/xpack/views/system_chat_user.py:102 #: apps/xpack/views/system_chat_user.py:113 #: apps/xpack/views/system_chat_user.py:131 #: apps/xpack/views/system_chat_user.py:146 #: apps/xpack/views/system_chat_user.py:163 #: apps/xpack/views/system_chat_user.py:180 #: apps/xpack/views/system_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:217 msgid "System/Chat user" msgstr "" #: apps/xpack/views/system_chat_user.py:73 #: apps/xpack/views/system_chat_user.py:74 #: apps/xpack/views/system_chat_user.py:75 msgid "Get chat user list" msgstr "" #: apps/xpack/views/system_chat_user.py:214 #: apps/xpack/views/system_chat_user.py:216 msgid "Sync chat users" msgstr "" #: apps/xpack/views/system_chat_user.py:215 msgid "Sync chat users from external source" msgstr "" #: apps/xpack/views/system_chat_user.py:235 #: apps/xpack/views/system_chat_user.py:247 #: apps/xpack/views/system_chat_user.py:261 #: apps/xpack/views/system_chat_user.py:279 #: apps/xpack/views/system_chat_user.py:301 #: apps/xpack/views/system_chat_user.py:321 msgid "System/User Group" msgstr "" #: apps/xpack/views/system_chat_user_group.py:19 #: apps/xpack/views/system_chat_user_group.py:20 #: apps/xpack/views/system_chat_user_group.py:21 msgid "Get Resource chat user group List" msgstr "" #: apps/xpack/views/system_chat_user_group.py:39 #: apps/xpack/views/system_chat_user_group.py:40 #: apps/xpack/views/system_chat_user_group.py:41 msgid "Edit Resource chat user group List" msgstr "" #: apps/xpack/views/system_chat_user_group.py:62 #: apps/xpack/views/system_chat_user_group.py:64 msgid "Get Resource chat user group page List" msgstr "" #: apps/xpack/views/system_chat_user_group.py:63 msgid "Get Resource chat user page group List" msgstr "" #: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23 #: apps/xpack/views/system_params.py:24 msgid "View appearance settings" msgstr "" #: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40 #: apps/xpack/views/system_params.py:41 msgid "Update appearance settings" msgstr "" msgid "Application Access" msgstr "Agent Access" msgid "Display execution details" msgstr "" msgid "LOCAL" msgstr "Account login" msgid "CAS" msgstr "CAS" msgid "LDAP" msgstr "LDAP" msgid "OIDC" msgstr "OIDC" msgid "OAuth2" msgstr "OAuth2" msgid "dingtalk" msgstr "DingTalk" msgid "wecom" msgstr "WeCom" msgid "lark" msgstr "Lark" msgid "Get tool list" msgstr "" msgid "Setting" msgstr "" msgid "Get verification results" msgstr "" msgid "Validation" msgstr "" msgid "Models Resource" msgstr "" msgid "Tools Resource" msgstr "" msgid "Get resource model list" msgstr "" msgid "System Model" msgstr "" msgid "Dialogue users" msgstr "" msgid "Conversation log" msgstr "" msgid "Public access link" msgstr "" msgid "User management" msgstr "User Management" msgid "Chat User/logout" msgstr "" msgid "Paragraph" msgstr "" msgid "User group" msgstr "" msgid "Remove member from user group" msgstr "" msgid "Create a web site knowledge base" msgstr "" msgid "Modify knowledge base information" msgstr "" msgid "Delete knowledge base" msgstr "" msgid "model" msgstr "Model" msgid "Batch add user to group" msgstr "" msgid "Add internal tool" msgstr "" msgid "Batch generate related" msgstr "" msgid "Update personal system API_KEY" msgstr "" msgid "Delete user group" msgstr "" msgid "Add user" msgstr "" msgid "folder" msgstr "Folder" msgid "Create or update user group" msgstr "" msgid "Edit folder" msgstr "" msgid "Email settings" msgstr "" msgid "trial listening" msgstr "" msgid "Add member to user group" msgstr "" msgid "System" msgstr "" msgid "Shared Knowledge/Document" msgstr "" msgid "System Application" msgstr "" msgid "Hit-Test" msgstr "" msgid "Export Application" msgstr "" msgid "Add ApiKey" msgstr "" msgid "Delete application API_KEY" msgstr "Delete agent API_KEY" msgid "knowledge Base" msgstr "" msgid "API KEY" msgstr "" msgid "Download" msgstr "" msgid "Delete personal system API_KEY" msgstr "" msgid "Add personal system API_KEY" msgstr "" msgid "Generate related documents" msgstr "" msgid "Modify application access token" msgstr "Modify agent access token" msgid "File not exist. Only manually uploaded documents are supported" msgstr "" msgid "Resource" msgstr "Resource Management" msgid "LDAP configuration not found or not active" msgstr "" msgid "Lark configuration not found or not active" msgstr "" msgid "Failed to get Lark collaborators" msgstr "" msgid "Failed to get Lark user details" msgstr "" msgid "WeCom configuration not found or not active" msgstr "" msgid "Failed to get WeCom access token" msgstr "" msgid "Failed to get WeCom agent info" msgstr "" msgid "Failed to get WeCom department users" msgstr "" msgid "Failed to get WeCom user info" msgstr "" msgid "Publish status" msgstr "" msgid "Unpublished" msgstr "" msgid "Published" msgstr "" msgid "users_permission" msgstr "" msgid "Get user authorization status of resource" msgstr "" msgid "Edit user authorization status of resource" msgstr "" msgid "Get user authorization status of resource by page" msgstr "" msgid "Obtain resource authorization list by page" msgstr "" msgid "Engine model type" msgstr "" msgid "If not passed, the default value is 16k_zh (Chinese universal)" msgstr "" msgid "Chinese telephone universal" msgstr "" msgid "English telephone universal" msgstr "" msgid "Commonly used in Chinese" msgstr "" msgid "Chinese, English, and Guangdong" msgstr "" msgid "Chinese medical" msgstr "" msgid "English" msgstr "" msgid "Cantonese" msgstr "" msgid "Japanese" msgstr "" msgid "Korean" msgstr "" msgid "Vietnamese" msgstr "" msgid "Malay language" msgstr "" msgid "Indonesian language" msgstr "" msgid "Filipino language" msgstr "" msgid "Thai" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Arabic" msgstr "" msgid "Spanish" msgstr "" msgid "Hindi" msgstr "" msgid "French" msgstr "" msgid "German" msgstr "" msgid "Multiple dialects, supporting 23 dialects" msgstr "" msgid "This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects." msgstr "" msgid "CueWord" msgstr "" msgid "If not passed, the default value is What is this audio saying? Only answer the audio content" msgstr "" msgid "The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text." msgstr "" msgid "resource authorization" msgstr "" msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition." msgstr "" msgid "If not passed, the default value is 'zh'" msgstr "" msgid "System resources authorization" msgstr "" msgid "This folder contains resources that you dont have permission" msgstr "" msgid "Text to Video" msgstr "" msgid "Image to Video" msgstr "" msgid "Authentication failed. Please verify that the parameters are correct" msgstr "" msgid "Chat context" msgstr "" msgid "Prompt template" msgstr "" msgid "generate prompt" msgstr "" msgid "Watermark" msgstr "" msgid "Whether to add watermark" msgstr "" msgid "Resolution" msgstr "" msgid "Ratio" msgstr "" msgid "Duration" msgstr "" msgid "Failed to generate video" msgstr "" msgid "password" msgstr "Password login" msgid "Failed to obtain the image" msgstr "" msgid "Update auth setting" msgstr "" msgid "If not passed, the default value is streaming_asr_demo" msgstr "" msgid "If not passed, the default value is 16000" msgstr "" msgid "Sample Rate" msgstr "" msgid "Captcha is required" msgstr "" msgid "Tag" msgstr "" msgid "Tag Setting" msgstr "" msgid "Download Original Document" msgstr "" msgid "Replace Original Document" msgstr "" msgid "Update License" msgstr "" msgid "Tag Key" msgstr "" msgid "Tag Value" msgstr "" msgid "Tag id does not exist" msgstr "" msgid "Tag key already exists" msgstr "" msgid "Tag value already exists" msgstr "" msgid "Non-existent id" msgstr "" msgid "No permission for the target folder" msgstr "" msgid "Application token usage statistics" msgstr "Agent token usage statistics" msgid "Application top question statistics" msgstr "Agent top question statistics" msgid "SAML2 Metadata" msgstr "" msgid "SAML2 Log in" msgstr "" msgid "SAML2 SSO" msgstr "" msgid "Workflow" msgstr "" msgid "Web source url" msgstr "" msgid "Web knowledge selector" msgstr "" msgid "The default is body, you can enter .classname/#idname/tagname" msgstr "" msgid "Please enter the Web root address" msgstr "" msgid "File size exceeds limit" msgstr "" msgid "File upload is not enabled" msgstr "" msgid "Blocked unsafe redirect to internal host" msgstr "" msgid "Audio file recognition - Tongyi Qwen" msgstr "" msgid "Real-time speech recognition - Fun-ASR/Paraformer" msgstr "" msgid "Qwen-Omni" msgstr "" msgid "Super-humanoid: Lingxiaoxuan Flow" msgstr "" msgid "Super-humanoid: Lingyuyan Flow" msgstr "" msgid "Super-humanoid: Lingfeiyi Flow" msgstr "" msgid "Super-humanoid: Lingxiaoyue Flow" msgstr "" msgid "Super-humanoid: Sun Dasheng Flow" msgstr "" msgid "Super-humanoid: Lingyuzhao Flow" msgstr "" msgid "Super-humanoid: Lingxiaotang Flow" msgstr "" msgid "Super-humanoid: Lingxiaorong Flow" msgstr "" msgid "Super-humanoid: Xinyun Flow" msgstr "" msgid "Super-humanoid: Grant (EN)" msgstr "" msgid "Super-humanoid: Lila (EN)" msgstr "" msgid "Super-humanoid: Lingwanwan Pro" msgstr "" msgid "Super-humanoid: Yiyi Pro" msgstr "" msgid "Super-humanoid: Huifangnv Pro" msgstr "" msgid "Super-humanoid: Lingxiaoying Pro" msgstr "" msgid "Super-humanoid: Lingfeibo Pro" msgstr "" msgid "Super-humanoid: Lingyuyan Pro" msgstr "" msgid "Login failed %s times, account will be locked, you have %s more chances !" msgstr "" msgid "This account has been locked for %s minutes, please try again later" msgstr "" msgid "User does not have permission to use API Key" msgstr "" msgid "Import knowledge workflow" msgstr "" msgid "Export knowledge workflow" msgstr "" msgid "Role IDs cannot be empty" msgstr "" msgid "Some roles do not exist" msgstr "" msgid "Authorized pagination list for obtaining resources" msgstr "" msgid "Resources mapping" msgstr "" msgid "Batch set user roles" msgstr "" msgid "Role Setting cannot be empty" msgstr "" msgid "View related resources" msgstr "" msgid "Feedback reason" msgstr "" msgid "Other reason content" msgstr "" msgid "accurate" msgstr "accurate" msgid "complete" msgstr "complete" msgid "inaccurate" msgstr "inaccurate" msgid "incomplete" msgstr "incomplete" msgid "Secret key is invalid" msgstr "" msgid "Secret key is expired" msgstr "" msgid "Online Usage" msgstr "" msgid "API Call" msgstr "" msgid "Enterprise WeChat" msgstr "" msgid "WeChat Public Account" msgstr "" msgid "Lark" msgstr "Lark App" msgid "DingTalk" msgstr "DingTalk App" msgid "Enterprise WeChat Robot" msgstr "" msgid "Trigger" msgstr "" msgid "Slack" msgstr "Slack App" msgid "Root Directory" msgstr "" msgid "Create trigger" msgstr "" msgid "Get the trigger list" msgstr "" msgid "Get trigger details" msgstr "" msgid "Modify the trigger" msgstr "" msgid "Delete the trigger" msgstr "" msgid "Delete trigger in batches" msgstr "" msgid "Activate trigger in batches" msgstr "" msgid "Get the trigger list by page" msgstr "" msgid "Create trigger in source" msgstr "" msgid "Get the trigger list of source" msgstr "" msgid "Modify the task source trigger" msgstr "" msgid "Get Task source trigger details" msgstr "" msgid "Delete the task source trigger" msgstr "" msgid "Get the task list of triggers" msgstr "" msgid "Retrieve detailed records of tasks executed by the trigger." msgstr "" msgid "Get a paginated list of execution records for trigger tasks." msgstr "" msgid "%s must be an array" msgstr "" msgid "%s must not be empty" msgstr "" msgid "%s values must be between %s and %s" msgstr "" msgid "Invalid time format: %s, must be HH:MM (e.g., 09:00)" msgstr "" msgid "schedule_type must be one of %s" msgstr "" msgid "interval_value must be an integer greater than or equal to 1" msgstr "" msgid "interval_unit must be one of %s" msgstr "" msgid "body must be an array" msgstr "" msgid "Error trigger type" msgstr "" msgid "The following id does not exist: %s" msgstr "" msgid "%s must be a dict" msgstr "" msgid "input_field_list must be a dict" msgstr "" msgid "%s type requires %s field" msgstr "" msgid "trigger name" msgstr "" msgid "trigger description" msgstr "" msgid "trigger setting" msgstr "" msgid "Trigger ID" msgstr "" msgid "Trigger task can not be empty" msgstr "" msgid "%s id does not exist" msgstr "" msgid "Trigger id does not exist" msgstr "" msgid "Trigger not found" msgstr "" msgid "Trigger must have at least one task" msgstr "" msgid "Trigger task number must be one" msgstr "" msgid "Incorrect trigger task" msgstr "" msgid "Trigger task ID" msgstr "" msgid "Trigger task record ID" msgstr "" msgid "Trigger task record id does not exist" msgstr "" msgid "Order field" msgstr "" msgid "System Trigger" msgstr "" msgid "Get the System trigger list of source" msgstr "" msgid "Get System Task source trigger details" msgstr "" msgid "Modify the System task source trigger" msgstr "" msgid "Delete the System task source trigger" msgstr "" msgid "Invalid source type" msgstr "" msgid "Shared tool is not supported" msgstr "" msgid "Read Trigger" msgstr "" msgid "Create Trigger" msgstr "" msgid "Edit Trigger" msgstr "" msgid "Delete Trigger" msgstr "" msgid "Read execute record" msgstr "" msgid "ADMIN" msgstr "System Administrator" msgid "WORKSPACE_MANAGE" msgstr "Workspace Manager" msgid "USER" msgstr "Regular User" msgid "Generate share link" msgstr "Generate share link" msgid "Chat record link" msgstr "Chat record link" msgid "Get chat record by share link" msgstr "Get chat record by share link" msgid "Invalid chat record ids" msgstr "Invalid chat record ids" msgid "Share link does not exist" msgstr "Share link does not exist" msgid "Chat has been deleted" msgstr "Chat has been deleted" msgid "cron type requires cron_expression field" msgstr "cron type requires cron_expression field" msgid "Invalid cron expression: %s" msgstr "Invalid cron expression: %s" msgid "Batch Remove Documents from Tag" msgstr "Batch Remove Documents from Tag" msgid "Document does not belong to current knowledge" msgstr "Document does not belong to current knowledge" msgid "Move an application" msgstr "Move an application" ================================================ FILE: apps/locales/zh_CN/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-06-18 17:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: apps/application/api/application_api.py:21 #: apps/application/serializers/application.py:153 msgid "Workflow Objects" msgstr "工作流对象" #: apps/application/api/application_api.py:52 #: apps/application/api/application_chat.py:104 #: apps/application/api/application_chat_record.py:74 #: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79 #: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110 #: apps/xpack/api/user_group.py:61 msgid "Current page" msgstr "当前页" #: apps/application/api/application_api.py:59 #: apps/application/api/application_chat.py:111 #: apps/application/api/application_chat_record.py:81 #: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86 #: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117 #: apps/xpack/api/user_group.py:68 msgid "Page size" msgstr "每页大小" #: apps/application/api/application_api.py:66 #: apps/application/serializers/application.py:156 #: apps/application/serializers/application.py:195 #: apps/application/serializers/application.py:274 #: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139 #: apps/knowledge/serializers/knowledge.py:52 #: apps/knowledge/serializers/knowledge.py:59 #: apps/tools/serializers/tool.py:453 #: apps/xpack/serializers/dataset_lark_serializer.py:55 msgid "folder id" msgstr "文件夹 ID" #: apps/application/api/application_api.py:73 #: apps/application/serializers/application.py:149 #: apps/application/serializers/application.py:275 #: apps/application/serializers/application.py:282 #: apps/application/serializers/application.py:368 #| msgid "Application" msgid "Application Name" msgstr "智能体名称" #: apps/application/api/application_api.py:80 #: apps/application/serializers/application.py:152 #: apps/application/serializers/application.py:276 #: apps/application/serializers/application.py:283 #: apps/application/serializers/application.py:284 #: apps/application/serializers/application.py:370 #| msgid "Application/Version" msgid "Application Description" msgstr "智能体描述" #: apps/application/api/application_api.py:87 #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47 #: apps/application/serializers/application.py:99 #: apps/application/serializers/application.py:277 #: apps/application/serializers/application.py:295 #: apps/application/serializers/application.py:413 #: apps/application/serializers/application.py:540 #: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357 #: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52 #: apps/users/api/user.py:110 apps/users/api/user.py:126 #: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82 #: apps/workspace/serializers/workspace_serializers.py:270 #: apps/workspace/serializers/workspace_serializers.py:306 #: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92 #: apps/xpack/serializers/chat_user.py:301 msgid "User ID" msgstr "用户 ID" #: apps/application/api/application_chat.py:70 #: apps/application/serializers/application_chat.py:56 msgid "Minimum number of likes" msgstr "最小点赞数" #: apps/application/api/application_chat.py:76 #: apps/application/serializers/application_chat.py:58 msgid "Minimum number of clicks" msgstr "最小点击数" #: apps/application/api/application_chat.py:82 #: apps/application/flow/step_node/condition_node/i_condition_node.py:18 #: apps/application/serializers/application_chat.py:59 msgid "Comparator" msgstr "比较器" #: apps/application/api/application_chat_record.py:46 #: apps/application/api/application_chat_record.py:115 #: apps/application/serializers/application_chat.py:47 #: apps/application/serializers/application_chat_record.py:76 #| msgid "Chat" msgid "Chat ID" msgstr "对话 ID" #: apps/application/api/application_chat_record.py:53 #: apps/application/serializers/application_chat_record.py:77 msgid "Is it in order" msgstr "是否有序" #: apps/application/api/application_chat_record.py:122 msgid "Chat Record ID" msgstr "对话记录 ID" #: apps/application/api/application_chat_record.py:129 #: apps/shared/api/shared_knowledge.py:235 #: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47 #: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111 #| msgid "Knowledge" msgid "Knowledge ID" msgstr "知识库 ID" #: apps/application/api/application_chat_record.py:136 #: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86 #| msgid "Document" msgid "Document ID" msgstr "文档 ID" #: apps/application/api/application_chat_record.py:148 #| msgid "Paragraph list" msgid "Paragraph ID" msgstr "段落 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26 #| msgid "type error" msgid "Model type error" msgstr "模型类型错误" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36 #: apps/common/field/common.py:24 apps/common/field/common.py:37 #| msgid "type error" msgid "Message type error" msgstr "消息类型错误" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55 #| msgid "Question list" msgid "Conversation list" msgstr "对话列表" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13 #: apps/application/flow/step_node/question_node/i_question_node.py:18 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13 #: apps/application/serializers/application.py:101 #: apps/application/serializers/application.py:285 #: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76 #: apps/shared/api/shared_model.py:98 msgid "Model id" msgstr "模型ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29 #| msgid "Paragraph list" msgid "Paragraph List" msgstr "段落列表" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_chat_record.py:41 #: apps/application/serializers/application_chat_record.py:140 #: apps/application/serializers/application_chat_record.py:179 #: apps/application/serializers/application_chat_record.py:247 #: apps/application/serializers/application_chat_record.py:312 #: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114 #| msgid "User relation ID" msgid "Conversation ID" msgstr "对话 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62 #: apps/application/flow/step_node/application_node/i_application_node.py:14 #: apps/application/serializers/application_chat.py:182 #: apps/chat/serializers/chat.py:40 #| msgid "Question list" msgid "User Questions" msgstr "用户问题" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65 msgid "Post-processor" msgstr "后置处理器" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68 #| msgid "Create question" msgid "Completion Question" msgstr "完成问题" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70 msgid "Streaming Output" msgstr "流式输出" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71 #: apps/xpack/serializers/resource_chat_user.py:93 #| msgid "user id" msgid "Chat user id" msgstr "对话用户 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73 #| msgid "Create user" msgid "Chat user Type" msgstr "对话用户类型" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47 msgid "No reference segment settings" msgstr "无参考段设置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81 msgid "Model settings" msgstr "模型设置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28 #: apps/application/flow/step_node/question_node/i_question_node.py:29 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20 msgid "Model parameter settings" msgstr "模型参数设置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91 msgid "message type error" msgstr "消息类型错误" #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224 #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270 msgid "" "Sorry, the AI model is not configured. Please go to the application to set " "up the AI model first." msgstr "抱歉,AI 模型未配置,请先前往智能体设置 AI 模型。" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24 #: apps/application/serializers/application_chat_record.py:172 msgid "question" msgstr "问题" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27 msgid "History Questions" msgstr "历史问题" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18 #: apps/application/flow/step_node/question_node/i_question_node.py:24 msgid "Number of multi-round conversations" msgstr "多轮对话的数量" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37 msgid "Maximum length of the knowledge base paragraph" msgstr "知识库段落的最大长度" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16 #: apps/application/flow/step_node/question_node/i_question_node.py:21 #: apps/application/serializers/application.py:79 #: apps/application/serializers/application.py:124 #: apps/knowledge/serializers/common.py:72 msgid "Prompt word" msgstr "提示词" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41 msgid "System prompt words (role)" msgstr "系统提示词(角色)" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44 msgid "Completion problem" msgstr "完成问题" #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32 #: apps/application/serializers/application.py:215 msgid "Question completion prompt" msgstr "问题完成提示" #: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20 #: apps/application/serializers/common.py:87 #, python-brace-format msgid "" "() contains the user's question. Answer the guessed user's question based on " "the context ({question}) Requirement: Output a complete question and put it " "in the tag" msgstr " () 包含用户的问题。根据上下文({question})要求:输出一个完整的问题并提出在标签中" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27 msgid "System completes question text" msgstr "系统完成问题文本" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39 msgid "Dataset id list" msgstr "知识库 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33 msgid "List of document ids to exclude" msgstr "排除的文档 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36 msgid "List of exclusion vector ids" msgstr "排除的向量 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24 #: apps/application/serializers/application.py:84 #: apps/application/serializers/application_chat.py:185 msgid "Reference segment number" msgstr "引用分段数" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42 msgid "Similarity" msgstr "相似度" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30 #: apps/application/serializers/application.py:91 #: apps/knowledge/serializers/knowledge.py:100 #: apps/knowledge/serializers/knowledge.py:643 msgid "The type only supports embedding|keywords|blend" msgstr "类型仅支持嵌入|关键字|混合" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31 #: apps/application/serializers/application.py:92 msgid "Retrieval Mode" msgstr "检索模式" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31 #: apps/application/serializers/application.py:113 #: apps/application/serializers/application.py:657 #: apps/application/serializers/application.py:664 #: apps/application/serializers/application.py:671 #: apps/knowledge/serializers/document.py:643 #: apps/knowledge/serializers/knowledge.py:220 #: apps/models_provider/serializers/model_serializer.py:116 #: apps/models_provider/serializers/model_serializer.py:134 #: apps/models_provider/serializers/model_serializer.py:370 #: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32 #: apps/shared/serializers/shared_model.py:65 #: apps/shared/serializers/shared_model.py:82 msgid "Model does not exist" msgstr "模型不存在" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33 msgid "No permission to use this model {model_name}" msgstr "无权限使用此模型{model_name}" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42 msgid "" "The vector model of the associated knowledge base is inconsistent and the " "segmentation cannot be recalled." msgstr "关联的知识库的向量模型不一致,无法回调分段。" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44 msgid "The knowledge base setting is wrong, please reset the knowledge base" msgstr "知识库设置错误,请重置知识库" #: apps/application/flow/common.py:206 #, python-brace-format msgid "The branch {branch} of the {node} node needs to be connected" msgstr "需要连接 {node} 节点的 {branch} 分支" #: apps/application/flow/common.py:212 #, python-brace-format msgid "{node} Nodes cannot be considered as end nodes" msgstr "{node} 节点不能被视为结束节点" #: apps/application/flow/common.py:226 msgid "The starting node is required" msgstr "开始节点是必需的" #: apps/application/flow/common.py:228 msgid "There can only be one starting node" msgstr "只能有一个开始节点" #: apps/application/flow/common.py:236 msgid "The node {node} model does not exist" msgstr "节点 {node} 模型不存在" #: apps/application/flow/common.py:246 #, python-brace-format msgid "Node {node} is unavailable" msgstr "节点 {node} 不可用" #: apps/application/flow/common.py:252 #, python-brace-format msgid "The library ID of node {node} cannot be empty" msgstr "工具库 ID {node} 不能为空" #: apps/application/flow/common.py:255 #, python-brace-format msgid "The function library for node {node} is not available" msgstr "工具库 {node} 不可用" #: apps/application/flow/common.py:261 msgid "Basic information node is required" msgstr "基本信息节点是必需的" #: apps/application/flow/common.py:263 msgid "There can only be one basic information node" msgstr "只能有一个基本信息节点" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15 #: apps/application/flow/step_node/question_node/i_question_node.py:20 #: apps/users/api/user.py:35 apps/users/api/user.py:102 msgid "Role Setting" msgstr "角色设置" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26 #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25 #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30 #: apps/application/flow/step_node/function_node/i_function_node.py:48 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23 #: apps/application/flow/step_node/question_node/i_question_node.py:27 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16 msgid "Whether to return content" msgstr "是否返回内容" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33 msgid "Context Type" msgstr "上下文类型" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35 msgid "Whether to enable MCP" msgstr "是否启用 MCP" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36 msgid "MCP Server" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:12 #: apps/application/serializers/application.py:539 #: apps/application/serializers/application_access_token.py:44 #: apps/application/serializers/application_chat.py:38 #: apps/application/serializers/application_chat.py:54 #: apps/application/serializers/application_chat_record.py:43 #: apps/application/serializers/application_chat_record.py:75 #: apps/application/serializers/application_stats.py:35 #: apps/application/serializers/application_version.py:21 #: apps/application/serializers/application_version.py:67 #: apps/chat/serializers/chat.py:118 #: apps/chat/serializers/chat_authentication.py:80 #: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29 #: apps/xpack/api/platform.py:70 msgid "Application ID" msgstr "智能体 ID" #: apps/application/flow/step_node/application_node/i_application_node.py:15 msgid "API Input Fields" msgstr "API 输入字段" #: apps/application/flow/step_node/application_node/i_application_node.py:17 msgid "User Input Fields" msgstr "用户输入字段" #: apps/application/flow/step_node/application_node/i_application_node.py:18 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25 #: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391 msgid "picture" msgstr "图片" #: apps/application/flow/step_node/application_node/i_application_node.py:19 #: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12 #: apps/chat/serializers/chat.py:58 msgid "document" msgstr "文档" #: apps/application/flow/step_node/application_node/i_application_node.py:20 #: apps/chat/serializers/chat.py:59 msgid "Audio" msgstr "音频" #: apps/application/flow/step_node/application_node/i_application_node.py:22 #: apps/chat/serializers/chat.py:62 msgid "Child Nodes" msgstr "子节点" #: apps/application/flow/step_node/application_node/i_application_node.py:23 #: apps/application/flow/step_node/form_node/i_form_node.py:21 msgid "Form Data" msgstr "表单数据" #: apps/application/flow/step_node/application_node/i_application_node.py:57 msgid "" "Parameter value error: The uploaded document lacks file_id, and the document " "upload fails" msgstr "参数值错误:上传的文档缺少 file_id,文档上传失败" #: apps/application/flow/step_node/application_node/i_application_node.py:66 msgid "" "Parameter value error: The uploaded image lacks file_id, and the image " "upload fails" msgstr "参数值错误:上传的图片缺少 file_id,图片上传失败" #: apps/application/flow/step_node/application_node/i_application_node.py:76 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails." msgstr "参数值错误:上传的音频缺少 file_id,音频上传失败" #: apps/application/flow/step_node/condition_node/i_condition_node.py:19 #: apps/models_provider/api/provide.py:24 msgid "value" msgstr "值" #: apps/application/flow/step_node/condition_node/i_condition_node.py:20 msgid "Fields" msgstr "字段" #: apps/application/flow/step_node/condition_node/i_condition_node.py:24 msgid "Branch id" msgstr "分支 ID" #: apps/application/flow/step_node/condition_node/i_condition_node.py:25 msgid "Branch Type" msgstr "分支类型" #: apps/application/flow/step_node/condition_node/i_condition_node.py:26 msgid "Condition or|and" msgstr "条件 或|与" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20 msgid "Response Type" msgstr "响应类型" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21 #: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13 msgid "Reference Field" msgstr "引用字段" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23 msgid "Direct answer content" msgstr "直接回答内容" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31 msgid "Reference field cannot be empty" msgstr "引用字段不能为空" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33 msgid "Reference field error" msgstr "引用字段错误" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36 msgid "Content cannot be empty" msgstr "内容不能为空" #: apps/application/flow/step_node/form_node/i_form_node.py:19 msgid "Form Configuration" msgstr "表单配置" #: apps/application/flow/step_node/form_node/i_form_node.py:20 msgid "Form output content" msgstr "表单输出内容" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22 #: apps/application/flow/step_node/function_node/i_function_node.py:24 msgid "Variable Name" msgstr "变量名称" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23 #: apps/application/flow/step_node/function_node/i_function_node.py:34 msgid "Variable Value" msgstr "变量名称" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27 msgid "Library ID" msgstr "工具 ID" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36 msgid "The function has been deleted" msgstr "工具已被删除" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43 msgid "Field: {name} No value set" msgstr "字段:{name} 未设置值" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59 msgid "Field: {name} Type: {_type} Value: {value} Unsupported types" msgstr "字段:{name} 类型:{_type} 值:{value} 类型转换错误" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100 msgid "Field: {name} Type: {_type} Value: {value} Type error" msgstr "字段:{name} 类型:{_type} 值:{value} 类型转换错误" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96 #: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259 msgid "type error" msgstr "类型错误" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106 #: apps/tools/serializers/tool.py:398 msgid "Function does not exist" msgstr "工具不存在" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108 msgid "No permission to use this function {name}" msgstr "无权限使用此工具 {name}" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110 #, python-brace-format msgid "Function {name} is unavailable" msgstr "工具 {name} 不可用" #: apps/application/flow/step_node/function_node/i_function_node.py:25 msgid "Is this field required" msgstr "{keys} 是必填项" #: apps/application/flow/step_node/function_node/i_function_node.py:26 #: apps/knowledge/serializers/document.py:203 #: apps/tools/serializers/tool.py:120 msgid "type" msgstr "类型" #: apps/application/flow/step_node/function_node/i_function_node.py:28 msgid "The field only supports string|int|dict|array|float" msgstr "字段仅支持字符串|整数|字典|数组|浮点数" #: apps/application/flow/step_node/function_node/i_function_node.py:30 #: apps/folders/serializers/folder.py:106 #: apps/folders/serializers/folder.py:141 #: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124 msgid "source" msgstr "来源" #: apps/application/flow/step_node/function_node/i_function_node.py:32 #: apps/tools/serializers/tool.py:126 msgid "The field only supports custom|reference" msgstr "字段仅支持自定义|引用" #: apps/application/flow/step_node/function_node/i_function_node.py:40 msgid "{field}, this field is required." msgstr "{field_label} 字段是必填项" #: apps/application/flow/step_node/function_node/i_function_node.py:46 msgid "function" msgstr "工具内容" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14 msgid "Prompt word (positive)" msgstr "提示词 (正向)" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16 msgid "Prompt word (negative)" msgstr "提示词 (负向)" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20 msgid "Conversation storage type" msgstr "对话存储类型" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13 msgid "Mcp servers" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16 msgid "Mcp server" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18 msgid "Mcp tool" msgstr "Mcp 工具" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21 msgid "Tool parameters" msgstr "工具参数" #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33 msgid "Maximum number of words in a quoted segment" msgstr "引用段落的最大字数" #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27 #: apps/knowledge/serializers/knowledge.py:97 #: apps/knowledge/serializers/knowledge.py:640 msgid "similarity" msgstr "相似度" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18 msgid "The audio file cannot be empty" msgstr "音频文件不能为空" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails" msgstr "参数错误:上传的音频缺少 file_id,音频上传失败" #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18 msgid "Text content" msgstr "文本内容" #: apps/application/models/application_chat.py:79 #: apps/xpack/serializers/channel/chat_manage.py:94 #: apps/xpack/serializers/channel/chat_manage.py:152 msgid "" "Sorry, no relevant content was found. Please re-describe your problem or " "provide more information. " msgstr "不好意思,没有找到相关内容。请重新描述您的问题或提供更多信息。" #: apps/application/serializers/application.py:78 msgid "No reference status" msgstr "无参考状态" #: apps/application/serializers/application.py:86 msgid "Acquaintance" msgstr "相似度" #: apps/application/serializers/application.py:88 msgid "Maximum number of quoted characters" msgstr "引用字符的最大数量" #: apps/application/serializers/application.py:95 msgid "Segment settings not referenced" msgstr "引用段设置未引用" #: apps/application/serializers/application.py:104 #: apps/application/serializers/application_chat_record.py:176 #: apps/application/serializers/application_chat_record.py:252 #: apps/application/serializers/application_chat_record.py:317 msgid "Knowledge base id" msgstr "知识库" #: apps/application/serializers/application.py:105 msgid "Knowledge Base List" msgstr "知识库列表" #: apps/application/serializers/application.py:119 msgid "The knowledge base id does not exist" msgstr "知识库 ID 不存在" #: apps/application/serializers/application.py:126 msgid "Role prompts" msgstr "角色提示" #: apps/application/serializers/application.py:128 msgid "No citation segmentation prompt" msgstr "无引用段落提示" #: apps/application/serializers/application.py:130 msgid "Thinking process switch" msgstr "思考过程开关" #: apps/application/serializers/application.py:134 msgid "The thinking process begins to mark" msgstr "思考过程开始标记" #: apps/application/serializers/application.py:138 msgid "End of thinking process marker" msgstr "思考过程结束标记" #: apps/application/serializers/application.py:155 #: apps/application/serializers/application.py:203 #: apps/application/serializers/application.py:378 msgid "Opening remarks" msgstr "开始提示" #: apps/application/serializers/application.py:191 msgid "application name" msgstr "智能体名称" #: apps/application/serializers/application.py:194 msgid "application describe" msgstr "智能体描述" #: apps/application/serializers/application.py:197 #: apps/application/serializers/application.py:372 #: apps/common/constants/permission_constants.py:226 #: apps/common/constants/permission_constants.py:259 #: apps/common/constants/permission_constants.py:264 #: apps/models_provider/views/model.py:63 #: apps/models_provider/views/model.py:95 #: apps/models_provider/views/model.py:113 #: apps/models_provider/views/model.py:130 #: apps/models_provider/views/model.py:145 #: apps/models_provider/views/model.py:160 #: apps/models_provider/views/model.py:173 #: apps/models_provider/views/model.py:194 #: apps/models_provider/views/model.py:210 #: apps/models_provider/views/model_apply.py:29 #: apps/models_provider/views/model_apply.py:41 #: apps/models_provider/views/model_apply.py:53 #: apps/models_provider/views/provide.py:25 #: apps/models_provider/views/provide.py:48 #: apps/models_provider/views/provide.py:62 #: apps/models_provider/views/provide.py:80 #: apps/models_provider/views/provide.py:97 msgid "Model" msgstr "模型" #: apps/application/serializers/application.py:201 #: apps/application/serializers/application.py:376 msgid "Historical chat records" msgstr "历史聊天记录" #: apps/application/serializers/application.py:206 #: apps/application/serializers/application.py:380 msgid "Related Knowledge Base" msgstr "相关知识库" #: apps/application/serializers/application.py:213 #: apps/application/serializers/application.py:390 msgid "Question completion" msgstr "问题完成" #: apps/application/serializers/application.py:217 msgid "Application Type" msgstr "智能体类型" #: apps/application/serializers/application.py:221 msgid "Application type only supports SIMPLE|WORK_FLOW" msgstr "智能体类型仅支持 SIMPLE|WORK_FLOW" #: apps/application/serializers/application.py:226 #: apps/application/serializers/application.py:394 msgid "Model parameters" msgstr "模型参数" #: apps/application/serializers/application.py:228 #: apps/application/serializers/application.py:396 msgid "Voice playback enabled" msgstr "开启语音播放" #: apps/application/serializers/application.py:230 #: apps/application/serializers/application.py:398 msgid "Voice playback model ID" msgstr "语音播放模型 ID" #: apps/application/serializers/application.py:232 #: apps/application/serializers/application.py:400 msgid "Voice playback type" msgstr "语音播放类型" #: apps/application/serializers/application.py:234 #: apps/application/serializers/application.py:402 msgid "Voice playback autoplay" msgstr "自动播放语音" #: apps/application/serializers/application.py:236 #: apps/application/serializers/application.py:404 msgid "Voice recognition enabled" msgstr "开启语音识别" #: apps/application/serializers/application.py:238 #: apps/application/serializers/application.py:406 msgid "Speech recognition model ID" msgstr "语音识别模型 ID" #: apps/application/serializers/application.py:240 #: apps/application/serializers/application.py:408 msgid "Voice recognition automatic transmission" msgstr "语音识别自动播放" #: apps/application/serializers/application.py:281 msgid "Primary key id" msgstr "" #: apps/application/serializers/application.py:286 msgid "Application type" msgstr "智能体类型" #: apps/application/serializers/application.py:287 #: apps/xpack/serializers/resource_chat_user.py:34 #: apps/xpack/serializers/resource_chat_user.py:110 #: apps/xpack/serializers/resource_chat_user_group.py:17 #: apps/xpack/serializers/resource_chat_user_group.py:85 msgid "Resource type" msgstr "资源类型" #: apps/application/serializers/application.py:288 msgid "Affiliation user" msgstr "关联用户" #: apps/application/serializers/application.py:289 msgid "Creation time" msgstr "创建时间" #: apps/application/serializers/application.py:290 msgid "Modification time" msgstr "修改时间" #: apps/application/serializers/application.py:294 #: apps/application/serializers/application_chat_record.py:42 #: apps/application/serializers/application_chat_record.py:323 #: apps/application/serializers/application_version.py:40 #: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147 #: apps/users/api/user.py:64 apps/users/api/user.py:170 #: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66 #: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103 #: apps/workspace/serializers/workspace_serializers.py:239 #: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40 #: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104 #: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63 msgid "Workspace ID" msgstr "工作空间 ID" #: apps/application/serializers/application.py:363 #: apps/knowledge/serializers/document.py:157 #: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57 #: apps/tools/serializers/tool.py:356 msgid "file" msgstr "文件" #: apps/application/serializers/application.py:384 msgid "Dataset settings" msgstr "知识库设置" #: apps/application/serializers/application.py:387 msgid "Model setup" msgstr "模型设置" #: apps/application/serializers/application.py:391 msgid "Icon" msgstr "" #: apps/application/serializers/application.py:412 #: apps/application/serializers/application_api_key.py:33 #: apps/application/serializers/application_api_key.py:64 #: apps/folders/serializers/folder.py:101 #: apps/folders/serializers/folder.py:140 #: apps/folders/serializers/folder.py:195 #: apps/knowledge/serializers/document.py:253 #: apps/knowledge/serializers/document.py:347 #: apps/knowledge/serializers/document.py:408 #: apps/knowledge/serializers/document.py:502 #: apps/knowledge/serializers/document.py:736 #: apps/knowledge/serializers/document.py:888 #: apps/knowledge/serializers/document.py:963 #: apps/knowledge/serializers/document.py:983 #: apps/knowledge/serializers/document.py:1166 #: apps/knowledge/serializers/knowledge.py:208 #: apps/knowledge/serializers/knowledge.py:448 #: apps/knowledge/serializers/knowledge.py:557 #: apps/knowledge/serializers/knowledge.py:635 #: apps/knowledge/serializers/paragraph.py:134 #: apps/knowledge/serializers/paragraph.py:346 #: apps/knowledge/serializers/paragraph.py:438 #: apps/knowledge/serializers/paragraph.py:558 #: apps/knowledge/serializers/problem.py:176 #: apps/knowledge/serializers/problem.py:204 #: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86 #: apps/models_provider/api/model.py:99 #: apps/models_provider/serializers/model_serializer.py:259 #: apps/models_provider/serializers/model_serializer.py:323 #: apps/models_provider/serializers/model_serializer.py:392 #: apps/shared/api/shared_knowledge.py:131 #: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60 #: apps/shared/api/shared_tool.py:147 #: apps/shared/serializers/shared_knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:109 #: apps/shared/serializers/shared_knowledge.py:157 #: apps/shared/serializers/shared_model.py:110 #: apps/shared/serializers/shared_tool.py:45 #: apps/shared/serializers/shared_tool.py:86 #: apps/system_manage/serializers/user_resource_permission.py:74 #: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210 #: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358 #: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425 #: apps/tools/serializers/tool.py:452 #: apps/xpack/serializers/dataset_lark_serializer.py:45 #: apps/xpack/serializers/resource_chat_user.py:33 #: apps/xpack/serializers/resource_chat_user.py:109 #: apps/xpack/serializers/resource_chat_user_group.py:16 #: apps/xpack/serializers/resource_chat_user_group.py:84 msgid "workspace id" msgstr "工作空间ID" #: apps/application/serializers/application.py:459 msgid "" "The community version supports up to 5 applications. If you need more " "applications, please contact us (https://fit2cloud.com/)." msgstr "" "社区版支持最多5个智能体,如需更多智能体,请联系我们(https://fit2cloud.com/)。" #: apps/application/serializers/application.py:471 #: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56 #: apps/common/handle/impl/text/zip_split_handle.py:69 #: apps/knowledge/serializers/document.py:864 #: apps/knowledge/serializers/document.py:871 #: apps/tools/serializers/tool.py:370 msgid "Unsupported file format" msgstr "不支持的文件格式" #: apps/application/serializers/application.py:545 msgid "Application id does not exist" msgstr "智能体 ID 不存在" #: apps/application/serializers/application.py:591 msgid "work_flow is a required field" msgstr "工作流是必填字段" #: apps/application/serializers/application.py:695 msgid "Unknown knowledge base id {dataset_id}, unable to associate" msgstr "未知知识库 ID {dataset_id},无法关联" #: apps/application/serializers/application_access_token.py:24 msgid "Reset Token" msgstr "重置令牌" #: apps/application/serializers/application_access_token.py:25 msgid "Is it enabled" msgstr "是否开启" #: apps/application/serializers/application_access_token.py:28 msgid "Number of visits" msgstr "访问次数" #: apps/application/serializers/application_access_token.py:30 msgid "Whether to enable whitelist" msgstr "是否启用白名单" #: apps/application/serializers/application_access_token.py:32 #: apps/application/serializers/application_access_token.py:33 msgid "Whitelist" msgstr "白名单" #: apps/application/serializers/application_access_token.py:35 msgid "Whether to display knowledge sources" msgstr "是否展示知识来源" #: apps/application/serializers/application_access_token.py:37 #: apps/users/serializers/user.py:665 #: apps/xpack/serializers/application_setting_serializer.py:37 msgid "language" msgstr "语言" #: apps/application/serializers/application_api_key.py:21 msgid "Availability" msgstr "可用" #: apps/application/serializers/application_api_key.py:24 msgid "Is cross-domain allowed" msgstr "是否允许跨域" #: apps/application/serializers/application_api_key.py:28 msgid "Cross-domain address" msgstr "跨域地址" #: apps/application/serializers/application_api_key.py:29 msgid "Cross-domain list" msgstr "跨域列表" #: apps/application/serializers/application_api_key.py:34 #: apps/application/serializers/application_api_key.py:65 #: apps/knowledge/serializers/knowledge.py:72 #: apps/xpack/serializers/application_setting_serializer.py:77 #: apps/xpack/serializers/dataset_lark_serializer.py:295 msgid "application id" msgstr "智能体 ID" #: apps/application/serializers/application_api_key.py:41 #: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332 #: apps/xpack/serializers/application_setting_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:81 #: apps/xpack/serializers/platform_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:138 #: apps/xpack/serializers/platform_serializer.py:149 msgid "Application does not exist" msgstr "智能体不存在" #: apps/application/serializers/application_api_key.py:66 msgid "ApiKeyId" msgstr "ApiKey ID" #: apps/application/serializers/application_api_key.py:87 msgid "APIKey does not exist" msgstr "APIKey 不存在" #: apps/application/serializers/application_chat.py:33 msgid "chat id" msgstr "对话 ID" #: apps/application/serializers/application_chat.py:34 #: apps/application/serializers/application_chat.py:51 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_version.py:23 msgid "summary" msgstr "摘要" #: apps/application/serializers/application_chat.py:35 msgid "Chat User ID" msgstr "对话用户 ID" #: apps/application/serializers/application_chat.py:36 msgid "Chat User Type" msgstr "对话用户类型" #: apps/application/serializers/application_chat.py:37 msgid "Is delete" msgstr "删除" #: apps/application/serializers/application_chat.py:39 #: apps/application/serializers/application_stats.py:25 msgid "Number of conversations" msgstr "对话数量" #: apps/application/serializers/application_chat.py:40 #: apps/application/serializers/application_stats.py:29 msgid "Number of Likes" msgstr "点赞数量" #: apps/application/serializers/application_chat.py:41 #: apps/application/serializers/application_stats.py:31 msgid "Number of thumbs-downs" msgstr "点踩数量" #: apps/application/serializers/application_chat.py:42 msgid "Number of tags" msgstr "标签数量" #: apps/application/serializers/application_chat.py:46 msgid "Chat ID List" msgstr "对话 ID 列表" #: apps/application/serializers/application_chat.py:52 #: apps/application/serializers/application_stats.py:36 #: apps/xpack/serializers/operate_log_serializer.py:55 msgid "Start time" msgstr "开始时间" #: apps/application/serializers/application_chat.py:53 #: apps/application/serializers/application_stats.py:37 #: apps/xpack/serializers/operate_log_serializer.py:56 msgid "End time" msgstr "结束时间" #: apps/application/serializers/application_chat.py:61 msgid "Only supports and|or" msgstr "只支持 与|或" #: apps/application/serializers/application_chat.py:183 msgid "Problem after optimization" msgstr "优化后的问题" #: apps/application/serializers/application_chat.py:184 msgid "answer" msgstr "回答" #: apps/application/serializers/application_chat.py:184 msgid "User feedback" msgstr "用户反馈" #: apps/application/serializers/application_chat.py:186 msgid "Section title + content" msgstr "分段标题 + 内容" #: apps/application/serializers/application_chat.py:187 #: apps/application/views/application_chat_record.py:139 #: apps/application/views/application_chat_record.py:140 #: apps/application/views/application_chat_record.py:141 #: apps/common/constants/permission_constants.py:248 msgid "Annotation" msgstr "标注" #: apps/application/serializers/application_chat.py:187 msgid "Consuming tokens" msgstr "消耗的令牌" #: apps/application/serializers/application_chat.py:188 msgid "Time consumed (s)" msgstr "耗时 (s)" #: apps/application/serializers/application_chat.py:189 msgid "Question Time" msgstr "提问时间" #: apps/application/serializers/application_chat_record.py:44 #: apps/application/serializers/application_chat_record.py:143 #: apps/application/serializers/application_chat_record.py:250 #: apps/application/serializers/application_chat_record.py:315 #: apps/chat/serializers/chat.py:45 msgid "Conversation record id" msgstr "对话记录 ID" #: apps/application/serializers/application_chat_record.py:51 msgid "Application authentication information does not exist" msgstr "智能体认证信息不存在" #: apps/application/serializers/application_chat_record.py:53 msgid "Displaying knowledge sources is not enabled" msgstr "知识库来源展示未开启" #: apps/application/serializers/application_chat_record.py:70 #: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274 msgid "Conversation does not exist" msgstr "对话不存在" #: apps/application/serializers/application_chat_record.py:152 #: apps/application/serializers/application_chat_record.py:279 #: apps/application/serializers/application_chat_record.py:336 #: apps/chat/serializers/chat.py:205 msgid "Conversation record does not exist" msgstr "对话记录不存在" #: apps/application/serializers/application_chat_record.py:168 msgid "Section title" msgstr "章节标题" #: apps/application/serializers/application_chat_record.py:169 msgid "Paragraph content" msgstr "段落内容" #: apps/application/serializers/application_chat_record.py:177 #: apps/application/serializers/application_chat_record.py:254 #: apps/application/serializers/application_chat_record.py:319 msgid "Document id" msgstr "文档 ID" #: apps/application/serializers/application_chat_record.py:184 #: apps/application/serializers/application_chat_record.py:260 #: apps/knowledge/serializers/paragraph.py:246 msgid "The document id is incorrect" msgstr "文档 ID 不正确" #: apps/application/serializers/application_chat_record.py:203 msgid "Conversation records that do not exist" msgstr "对话记录不存在" #: apps/application/serializers/application_chat_record.py:321 msgid "Paragraph id" msgstr "段落 ID" #: apps/application/serializers/application_chat_record.py:340 #, python-brace-format msgid "" "The paragraph id is wrong. The current conversation record does not exist. " "[{paragraph_id}] paragraph id" msgstr "段落 ID 错误。当前对话记录不存在。[{paragraph_id}] 段落 ID" #: apps/application/serializers/application_stats.py:26 msgid "Number of new users" msgstr "新用户数量" #: apps/application/serializers/application_stats.py:27 msgid "Total number of users" msgstr "总用户数" #: apps/application/serializers/application_stats.py:28 msgid "date" msgstr "日期" #: apps/application/serializers/application_stats.py:30 msgid "Tokens consumption" msgstr "消耗的令牌" #: apps/application/serializers/application_version.py:36 msgid "Version Name" msgstr "版本名称" #: apps/application/serializers/application_version.py:69 msgid "Workflow version id" msgstr "工作流版本 ID" #: apps/application/serializers/application_version.py:79 #: apps/application/serializers/application_version.py:94 msgid "Workflow version does not exist" msgstr "工作流版本不存在" #: apps/application/views/application.py:41 #: apps/application/views/application.py:42 #: apps/application/views/application.py:43 msgid "Create an application" msgstr "创建一个智能体程序" #: apps/application/views/application.py:47 #: apps/application/views/application.py:65 #: apps/application/views/application.py:82 #: apps/application/views/application.py:103 #: apps/application/views/application.py:123 #: apps/application/views/application.py:145 #: apps/application/views/application.py:166 #: apps/application/views/application.py:187 #: apps/application/views/application.py:206 #: apps/application/views/application_access_token.py:32 #: apps/application/views/application_access_token.py:47 #: apps/application/views/application_chat.py:102 #: apps/application/views/application_chat.py:122 #: apps/application/views/application_stats.py:33 #: apps/common/constants/permission_constants.py:224 #: apps/common/constants/permission_constants.py:234 #: apps/xpack/views/application_setting.py:29 #: apps/xpack/views/application_setting.py:47 msgid "Application" msgstr "智能体" #: apps/application/views/application.py:60 #: apps/application/views/application.py:61 #: apps/application/views/application.py:62 msgid "Get the application list" msgstr "获取智能体列表" #: apps/application/views/application.py:77 #: apps/application/views/application.py:78 #: apps/application/views/application.py:79 msgid "Get the application list by page" msgstr "分页获取智能体列表" #: apps/application/views/application.py:97 #: apps/application/views/application.py:98 #: apps/application/views/application.py:99 msgid "Import Application" msgstr "导入智能体" #: apps/application/views/application.py:117 #: apps/application/views/application.py:118 #: apps/application/views/application.py:119 msgid "Export application" msgstr "导出智能体" #: apps/application/views/application.py:140 #: apps/application/views/application.py:141 #: apps/application/views/application.py:142 msgid "Deleting application" msgstr "删除智能体" #: apps/application/views/application.py:160 #: apps/application/views/application.py:161 #: apps/application/views/application.py:162 msgid "Modify the application" msgstr "修改智能体" #: apps/application/views/application.py:181 #: apps/application/views/application.py:182 #: apps/application/views/application.py:183 msgid "Get application details" msgstr "获取智能体详情" #: apps/application/views/application.py:200 #: apps/application/views/application.py:201 #: apps/application/views/application.py:202 msgid "Publishing an application" msgstr "发布智能体" #: apps/application/views/application_access_token.py:27 #: apps/application/views/application_access_token.py:28 #: apps/application/views/application_access_token.py:29 msgid "Modify application access restriction information" msgstr "修改智能体访问限制信息" #: apps/application/views/application_access_token.py:43 #: apps/application/views/application_access_token.py:44 #: apps/application/views/application_access_token.py:45 msgid "Get application access restriction information" msgstr "获取智能体访问限制信息" #: apps/application/views/application_api_key.py:31 #: apps/application/views/application_api_key.py:32 #: apps/application/views/application_api_key.py:33 msgid "Create application ApiKey" msgstr "创建智能体 API 密钥" #: apps/application/views/application_api_key.py:37 #: apps/application/views/application_api_key.py:57 #: apps/application/views/application_api_key.py:77 #: apps/application/views/application_api_key.py:99 msgid "Application Api Key" msgstr "智能体 API 密钥" #: apps/application/views/application_api_key.py:52 msgid "GET application ApiKey List" msgstr "获取智能体的 API 密钥列表" #: apps/application/views/application_api_key.py:53 #: apps/application/views/application_api_key.py:54 msgid "Create application ApiKey List" msgstr "创建智能体 API 密钥列表" #: apps/application/views/application_api_key.py:71 #: apps/application/views/application_api_key.py:72 #: apps/application/views/application_api_key.py:73 msgid "Modify application API_KEY" msgstr "修改智能体 API 密钥" #: apps/application/views/application_api_key.py:93 #: apps/application/views/application_api_key.py:94 #: apps/application/views/application_api_key.py:95 msgid "Delete Application API_KEY" msgstr "删除智能体 API 密钥" #: apps/application/views/application_chat.py:35 #: apps/application/views/application_chat.py:36 #: apps/application/views/application_chat.py:37 msgid "Get the conversation list" msgstr "获取对话列表" #: apps/application/views/application_chat.py:41 #: apps/application/views/application_chat.py:61 #: apps/application/views/application_chat.py:82 #: apps/application/views/application_chat_record.py:37 #: apps/application/views/application_chat_record.py:58 #: apps/application/views/application_chat_record.py:82 #: apps/application/views/application_chat_record.py:106 #: apps/application/views/application_chat_record.py:125 #: apps/application/views/application_chat_record.py:145 #: apps/application/views/application_chat_record.py:171 msgid "Application/Conversation Log" msgstr "智能体/对话日志" #: apps/application/views/application_chat.py:55 #: apps/application/views/application_chat.py:56 #: apps/application/views/application_chat.py:57 msgid "Get the conversation list by page" msgstr "分页获取对话列表" #: apps/application/views/application_chat.py:76 #: apps/application/views/application_chat.py:77 #: apps/application/views/application_chat.py:78 msgid "Export conversation" msgstr "导出对话" #: apps/application/views/application_chat.py:97 #: apps/application/views/application_chat.py:98 #: apps/application/views/application_chat.py:99 msgid "Get a temporary session id based on the application id" msgstr "获取智能体的临时会话 ID" #: apps/application/views/application_chat.py:116 #: apps/application/views/application_chat.py:117 #: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93 #: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95 msgid "dialogue" msgstr "对话" #: apps/application/views/application_chat_record.py:31 #: apps/application/views/application_chat_record.py:32 #: apps/application/views/application_chat_record.py:33 msgid "Get the conversation record list" msgstr "获取对话记录列表" #: apps/application/views/application_chat_record.py:52 #: apps/application/views/application_chat_record.py:53 #: apps/application/views/application_chat_record.py:54 msgid "Get the conversation record list by page" msgstr "分页获取对话记录列表" #: apps/application/views/application_chat_record.py:76 #: apps/application/views/application_chat_record.py:77 #: apps/application/views/application_chat_record.py:78 msgid "Get conversation record details" msgstr "获取对话记录详情" #: apps/application/views/application_chat_record.py:100 #: apps/application/views/application_chat_record.py:101 #: apps/application/views/application_chat_record.py:102 msgid "Add to Knowledge Base" msgstr "添加到知识库" #: apps/application/views/application_chat_record.py:119 #: apps/application/views/application_chat_record.py:120 #: apps/application/views/application_chat_record.py:121 msgid "Get the list of marked paragraphs" msgstr "获取标记段落列表" #: apps/application/views/application_chat_record.py:165 #: apps/application/views/application_chat_record.py:166 #: apps/application/views/application_chat_record.py:167 msgid "Delete a Annotation" msgstr "删除注释" #: apps/application/views/application_stats.py:28 #: apps/application/views/application_stats.py:29 #: apps/application/views/application_stats.py:30 msgid "Dialogue-related statistical trends" msgstr "与对话有关的统计趋势" #: apps/application/views/application_version.py:30 #: apps/application/views/application_version.py:31 #: apps/application/views/application_version.py:32 msgid "Get the application version list" msgstr "获取智能体版本列表" #: apps/application/views/application_version.py:35 #: apps/application/views/application_version.py:55 #: apps/application/views/application_version.py:76 #: apps/application/views/application_version.py:94 msgid "Application/Version" msgstr "智能体/ 版本" #: apps/application/views/application_version.py:50 #: apps/application/views/application_version.py:51 #: apps/application/views/application_version.py:52 msgid "Get the list of application versions by page" msgstr "分页获取智能体版本列表" #: apps/application/views/application_version.py:71 #: apps/application/views/application_version.py:72 #: apps/application/views/application_version.py:73 msgid "Get application version details" msgstr "获取智能体版本详情" #: apps/application/views/application_version.py:88 #: apps/application/views/application_version.py:89 #: apps/application/views/application_version.py:90 msgid "Modify application version information" msgstr "修改智能体版本信息" #: apps/chat/api/chat_authentication_api.py:38 #: apps/chat/serializers/chat_authentication.py:28 #: apps/chat/serializers/chat_authentication.py:54 #: apps/xpack/serializers/chat_auth.py:25 msgid "access_token" msgstr "" #: apps/chat/api/chat_embed_api.py:24 msgid "host" msgstr "" #: apps/chat/api/chat_embed_api.py:31 #: apps/chat/serializers/chat_embed_serializers.py:25 msgid "protocol" msgstr "协议" #: apps/chat/api/chat_embed_api.py:38 #: apps/chat/serializers/chat_embed_serializers.py:26 #: apps/users/serializers/login.py:36 msgid "token" msgstr "令牌" #: apps/chat/serializers/chat.py:42 msgid "Is the answer in streaming mode" msgstr "是否流式回答" #: apps/chat/serializers/chat.py:43 msgid "Do you want to reply again" msgstr "是否重新回复" #: apps/chat/serializers/chat.py:48 msgid "Node id" msgstr "节点 ID" #: apps/chat/serializers/chat.py:51 msgid "Runtime node id" msgstr "运行时节点 ID" #: apps/chat/serializers/chat.py:54 msgid "Node parameters" msgstr "节点参数" #: apps/chat/serializers/chat.py:56 msgid "Global variables" msgstr "全局变量" #: apps/chat/serializers/chat.py:60 #: apps/common/constants/permission_constants.py:222 #: apps/common/constants/permission_constants.py:228 msgid "Other" msgstr "其他" #: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320 msgid "Client id" msgstr "客户端 ID" #: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321 msgid "Client Type" msgstr "客户端类型" #: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322 #: apps/common/constants/permission_constants.py:240 msgid "Debug" msgstr "调试" #: apps/chat/serializers/chat.py:146 msgid "The number of visits exceeds today's visits" msgstr "今天的访问次数超过限制" #: apps/chat/serializers/chat.py:157 msgid "The current model is not available" msgstr "当前模型不可用" #: apps/chat/serializers/chat.py:159 msgid "The model is downloading, please try again later" msgstr "下载过程被中断,请重试" #: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357 msgid "The application has not been published. Please use it after publishing." msgstr "智能体未发布,请发布后使用。" #: apps/chat/serializers/chat_authentication.py:50 #: apps/xpack/serializers/chat_auth.py:53 msgid "Invalid access_token" msgstr "access_token 无效" #: apps/chat/serializers/chat_authentication.py:89 msgid "Illegal User" msgstr "非法用户" #: apps/chat/serializers/chat_embed_serializers.py:24 msgid "Host" msgstr "" #: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38 #: apps/chat/views/chat.py:39 msgid "Application Anonymous Certification" msgstr "智能体匿名认证" #: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64 #: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99 #: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27 #: apps/xpack/views/chat_user_auth.py:419 msgid "Chat" msgstr "对话" #: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60 #: apps/chat/views/chat.py:61 msgid "Get application related information" msgstr "获取智能体相关信息" #: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77 #: apps/chat/views/chat.py:78 msgid "Get application authentication information" msgstr "获取智能体认证信息" #: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116 #: apps/chat/views/chat.py:117 msgid "Get the session id according to the application id" msgstr "根据智能体 ID 获取会话 ID" #: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132 #: apps/chat/views/chat.py:133 apps/users/views/login.py:70 #: apps/users/views/login.py:71 apps/users/views/login.py:72 msgid "Get captcha" msgstr "获取验证码" #: apps/chat/views/chat.py:134 #: apps/common/constants/permission_constants.py:210 #: apps/users/views/login.py:41 apps/users/views/login.py:58 #: apps/users/views/login.py:73 apps/users/views/user.py:63 #: apps/users/views/user.py:77 apps/users/views/user.py:91 #: apps/users/views/user.py:108 apps/users/views/user.py:123 #: apps/users/views/user.py:136 apps/users/views/user.py:150 #: apps/users/views/user.py:164 apps/users/views/user.py:180 #: apps/users/views/user.py:193 apps/users/views/user.py:206 #: apps/users/views/user.py:217 apps/users/views/user.py:235 #: apps/users/views/user.py:251 apps/users/views/user.py:269 #: apps/users/views/user.py:286 apps/users/views/user.py:303 #: apps/users/views/user.py:321 apps/users/views/user.py:338 #: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206 msgid "User Management" msgstr "用户管理" #: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23 #: apps/chat/views/chat_embed.py:24 msgid "Get embedded js" msgstr "获取嵌入式 JavaScript" #: apps/common/auth/authenticate.py:80 msgid "Not logged in, please log in first" msgstr "未登录,请先登录" #: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89 #: apps/common/auth/authenticate.py:95 msgid "Authentication information is incorrect! illegal user" msgstr "身份验证信息不正确!非法用户" #: apps/common/auth/authentication.py:98 msgid "No permission to access" msgstr "无权限访问" #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49 #: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43 #: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49 msgid "Authentication information is incorrect" msgstr "身份验证信息不正确" #: apps/common/auth/handle/impl/user_token.py:265 msgid "Login expired" msgstr "登录已过期" #: apps/common/constants/exception_code_constants.py:31 #: apps/users/serializers/login.py:53 #: apps/xpack/serializers/chat_user_serializer.py:123 msgid "The username or password is incorrect" msgstr "用户名或密码不正确" #: apps/common/constants/exception_code_constants.py:32 msgid "Please log in first and bring the user Token" msgstr "请先登录并携带用户 Token" #: apps/common/constants/exception_code_constants.py:33 #: apps/users/serializers/user.py:630 msgid "Email sending failed" msgstr "邮件发送失败" #: apps/common/constants/exception_code_constants.py:34 msgid "Email format error" msgstr "邮箱格式错误" #: apps/common/constants/exception_code_constants.py:35 msgid "The email has been registered, please log in directly" msgstr "该邮箱已注册,请直接登录" #: apps/common/constants/exception_code_constants.py:36 msgid "The email is not registered, please register first" msgstr "该邮箱未注册,请先注册" #: apps/common/constants/exception_code_constants.py:38 msgid "The verification code is incorrect or the verification code has expired" msgstr "验证码不正确或已过期" #: apps/common/constants/exception_code_constants.py:39 msgid "The username has been registered, please log in directly" msgstr "用户名已注册,请直接登录" #: apps/common/constants/exception_code_constants.py:41 msgid "" "The username cannot be empty and must be between 6 and 20 characters long." msgstr "用户名不能为空,且长度在6到20个字符之间。" #: apps/common/constants/exception_code_constants.py:43 msgid "Password and confirmation password are inconsistent" msgstr "密码和确认密码不一致" #: apps/common/constants/exception_code_constants.py:44 msgid "The nickname is already registered" msgstr "姓名已注册" #: apps/common/constants/permission_constants.py:209 msgid "System Setting" msgstr "系统设置" #: apps/common/constants/permission_constants.py:211 #: apps/common/constants/permission_constants.py:272 #: apps/role_setting/views/role_setting.py:44 #: apps/role_setting/views/role_setting.py:67 #: apps/role_setting/views/role_setting.py:84 #: apps/role_setting/views/role_setting.py:103 #: apps/role_setting/views/role_setting.py:125 #: apps/role_setting/views/role_setting.py:145 #: apps/role_setting/views/role_setting.py:167 #: apps/role_setting/views/role_setting.py:191 #: apps/role_setting/views/role_setting.py:210 msgid "Role" msgstr "角色" #: apps/common/constants/permission_constants.py:212 #: apps/common/constants/permission_constants.py:270 #: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49 #: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80 #: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119 #: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155 #: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188 #: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223 #: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250 msgid "Workspace" msgstr "工作空间" #: apps/common/constants/permission_constants.py:213 msgid "Resource Application" msgstr "资源管理-智能体" #: apps/common/constants/permission_constants.py:214 msgid "Resource Knowledge" msgstr "资源管理-知识库" #: apps/common/constants/permission_constants.py:215 msgid "Resource Tool" msgstr "资源管理-工具" #: apps/common/constants/permission_constants.py:216 msgid "Resource Model" msgstr "资源管理-模型" #: apps/common/constants/permission_constants.py:217 msgid "Resource Permission" msgstr "资源授权" #: apps/common/constants/permission_constants.py:218 #: apps/shared/views/shared_dataset_lark_views.py:30 #: apps/shared/views/shared_dataset_lark_views.py:50 #: apps/shared/views/shared_knowledge.py:33 #: apps/shared/views/shared_knowledge.py:53 #: apps/shared/views/shared_knowledge.py:76 #: apps/shared/views/shared_knowledge.py:91 #: apps/shared/views/shared_knowledge.py:106 #: apps/shared/views/shared_knowledge.py:125 #: apps/shared/views/shared_knowledge.py:151 #: apps/shared/views/shared_knowledge.py:178 #: apps/shared/views/shared_knowledge.py:196 #: apps/shared/views/shared_knowledge.py:214 #: apps/shared/views/shared_knowledge.py:235 #: apps/shared/views/shared_knowledge.py:256 #: apps/shared/views/shared_knowledge.py:276 #: apps/shared/views/shared_knowledge.py:297 #: apps/shared/views/shared_knowledge.py:312 #: apps/shared/views/shared_knowledge.py:331 #: apps/shared/views/shared_knowledge.py:354 #: apps/shared/views/shared_knowledge.py:386 #: apps/shared/views/shared_knowledge.py:407 msgid "Shared Knowledge" msgstr "共享资源-知识库" #: apps/common/constants/permission_constants.py:219 #: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58 #: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107 #: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138 #: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166 #: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202 #: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234 #: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270 msgid "Shared Model" msgstr "共享资源-模型" #: apps/common/constants/permission_constants.py:220 #: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49 #: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83 #: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116 #: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157 #: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194 #: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239 #: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273 #: apps/shared/views/shared_tool.py:294 msgid "Shared Tool" msgstr "共享资源-工具" #: apps/common/constants/permission_constants.py:221 msgid "Operation Log" msgstr "操作日志" #: apps/common/constants/permission_constants.py:223 msgid "System Management" msgstr "系统管理" #: apps/common/constants/permission_constants.py:225 #: apps/common/constants/permission_constants.py:235 #: apps/common/constants/permission_constants.py:260 #: apps/common/constants/permission_constants.py:265 msgid "Knowledge" msgstr "知识库" #: apps/common/constants/permission_constants.py:227 #: apps/common/constants/permission_constants.py:258 #: apps/common/constants/permission_constants.py:263 #: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61 #: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104 #: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146 #: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201 #: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250 #: apps/tools/views/tool.py:274 msgid "Tool" msgstr "工具" #: apps/common/constants/permission_constants.py:229 msgid "Read" msgstr "查看" #: apps/common/constants/permission_constants.py:230 msgid "Edit" msgstr "编辑" #: apps/common/constants/permission_constants.py:231 msgid "Create" msgstr "创建" #: apps/common/constants/permission_constants.py:232 msgid "Delete" msgstr "删除" #: apps/common/constants/permission_constants.py:233 msgid "Email Setting" msgstr "邮箱设置" #: apps/common/constants/permission_constants.py:236 #: apps/common/constants/permission_constants.py:261 #: apps/common/constants/permission_constants.py:266 msgid "Document" msgstr "文档" #: apps/common/constants/permission_constants.py:237 #: apps/common/constants/permission_constants.py:262 #: apps/common/constants/permission_constants.py:267 msgid "Problem" msgstr "问题" #: apps/common/constants/permission_constants.py:238 msgid "Import" msgstr "导入" #: apps/common/constants/permission_constants.py:239 msgid "Export" msgstr "导出" #: apps/common/constants/permission_constants.py:241 msgid "Sync" msgstr "同步" #: apps/common/constants/permission_constants.py:242 msgid "Generate" msgstr "生成问题" #: apps/common/constants/permission_constants.py:243 msgid "Add Member" msgstr "添加成员" #: apps/common/constants/permission_constants.py:244 msgid "Remove Member" msgstr "移除成员" #: apps/common/constants/permission_constants.py:245 msgid "Vector" msgstr "向量化" #: apps/common/constants/permission_constants.py:246 msgid "Migrate" msgstr "迁移" #: apps/common/constants/permission_constants.py:247 msgid "Relate" msgstr "关联分段" #: apps/common/constants/permission_constants.py:249 msgid "Clear Policy" msgstr "清除策略" #: apps/common/constants/permission_constants.py:250 msgid "Login Auth" msgstr "登录认证" #: apps/common/constants/permission_constants.py:251 msgid "Display Settings" msgstr "显示设置" #: apps/common/constants/permission_constants.py:252 #: apps/common/constants/permission_constants.py:720 #: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38 #: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71 msgid "System API Key" msgstr "系统 API Key" #: apps/common/constants/permission_constants.py:253 #: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42 msgid "Appearance Settings" msgstr "外观设置" #: apps/common/constants/permission_constants.py:254 #: apps/common/constants/permission_constants.py:269 #: apps/xpack/views/system_chat_user.py:339 #: apps/xpack/views/system_chat_user.py:362 msgid "Chat User" msgstr "对话用户" #: apps/common/constants/permission_constants.py:255 #: apps/common/constants/permission_constants.py:268 msgid "User Group" msgstr "用户组" #: apps/common/constants/permission_constants.py:256 msgid "Chat User Auth" msgstr "对话用户认证" #: apps/common/constants/permission_constants.py:257 msgid "Overview" msgstr "概览" #: apps/common/constants/permission_constants.py:271 #: apps/common/constants/permission_constants.py:671 #: apps/common/constants/permission_constants.py:677 #: apps/common/constants/permission_constants.py:683 #: apps/common/constants/permission_constants.py:689 msgid "Dialogue log" msgstr "对话日志" #: apps/common/constants/permission_constants.py:641 msgid "Embed third party" msgstr "嵌入第三方" #: apps/common/constants/permission_constants.py:647 msgid "Access restrictions" msgstr "访问限制" #: apps/common/constants/permission_constants.py:653 msgid "Display settings" msgstr "显示设置" #: apps/common/constants/permission_constants.py:659 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75 msgid "API Key" msgstr "" #: apps/common/constants/permission_constants.py:665 msgid "Public settings" msgstr "公共访问连接" #: apps/common/constants/permission_constants.py:704 msgid "About" msgstr "关于" #: apps/common/constants/permission_constants.py:709 #: apps/users/views/user.py:88 apps/users/views/user.py:89 #: apps/users/views/user.py:90 msgid "Switch Language" msgstr "切换语言" #: apps/common/constants/permission_constants.py:714 msgid "Change Password" msgstr "修改密码" #: apps/common/constants/permission_constants.py:734 msgid "Sync users" msgstr "同步用户" #: apps/common/constants/permission_constants.py:755 #: apps/common/constants/permission_constants.py:808 msgid "Set up user groups" msgstr "设置用户组" #: apps/common/event/__init__.py:27 msgid "The download process was interrupted, please try again" msgstr "下载过程被中断,请重试" #: apps/common/event/listener_manage.py:90 #, python-brace-format msgid "Query vector data: {paragraph_id_list} error {error} {traceback}" msgstr "查询向量数据:{paragraph_id_list} 错误:{error} {traceback}" #: apps/common/event/listener_manage.py:95 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id_list}" msgstr "开始--->向量段落: {paragraph_id_list}" #: apps/common/event/listener_manage.py:107 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id_list} error {error} {traceback}" msgstr "向量段落: {paragraph_id_list} 错误:{error} {traceback}" #: apps/common/event/listener_manage.py:113 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id_list}" msgstr "结束--->向量段落: {paragraph_id_list}" #: apps/common/event/listener_manage.py:122 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id}" msgstr "开始--->向量段落: {paragraph_id}" #: apps/common/event/listener_manage.py:147 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id} error {error} {traceback}" msgstr "向量段落: {paragraph_id} 错误:{error} {traceback}" #: apps/common/event/listener_manage.py:152 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id}" msgstr "结束--->向量段落: {paragraph_id}" #: apps/common/event/listener_manage.py:268 #, python-brace-format msgid "Start--->Embedding document: {document_id}" msgstr "开始--->向量文档: {document_id}" #: apps/common/event/listener_manage.py:288 #, python-brace-format msgid "Vectorized document: {document_id} error {error} {traceback}" msgstr "向量文档: {document_id} 错误:{error} {traceback}" #: apps/common/event/listener_manage.py:293 #, python-brace-format msgid "End--->Embedding document: {document_id}" msgstr "结束--->向量文档: {document_id}" #: apps/common/event/listener_manage.py:304 #, python-brace-format msgid "Start--->Embedding knowledge: {knowledge_id}" msgstr "开始--->向量知识库: {knowledge_id}" #: apps/common/event/listener_manage.py:308 #, python-brace-format msgid "Start--->Embedding document: {document_list}" msgstr "开始--->向量文档: {document_list}" #: apps/common/event/listener_manage.py:312 #: apps/knowledge/task/embedding.py:116 #, python-brace-format msgid "Vectorized knowledge: {knowledge_id} error {error} {traceback}" msgstr "向量知识库: {knowledge_id} 错误:{error} {traceback}" #: apps/common/event/listener_manage.py:315 #, python-brace-format msgid "End--->Embedding knowledge: {knowledge_id}" msgstr "结束--->向量知识库: {knowledge_id}" #: apps/common/exception/handle_exception.py:32 #: apps/common/handle/handle_exception.py:33 msgid "Unknown exception" msgstr "未知错误" #: apps/common/field/common.py:48 msgid "not a function" msgstr "不是函数" #: apps/common/forms/base_field.py:64 #, python-brace-format msgid "The field {field_label} is required" msgstr "{field_label} 字段是必填项" #: apps/common/forms/slider_field.py:56 #, python-brace-format msgid "The {field_label} cannot be less than {min}" msgstr "{field_label} 不能小于{min}" #: apps/common/forms/slider_field.py:62 #, python-brace-format msgid "The {field_label} cannot be greater than {max}" msgstr "{field_label} 不能大于{max}" #: apps/common/handle/impl/text/pdf_split_handle.py:281 #, python-brace-format msgid "This document has no preface and is treated as ordinary text: {e}" msgstr "该文档没有前言,视为普通文本: {e}" #: apps/common/job/clean_chat_job.py:23 msgid "start clean chat log" msgstr "开始清理聊天日志" #: apps/common/job/clean_chat_job.py:69 msgid "end clean chat log" msgstr "结束清理聊天日志" #: apps/common/job/clean_debug_file_job.py:21 msgid "start clean debug file" msgstr "开始清理调试文件" #: apps/common/job/clean_debug_file_job.py:25 msgid "end clean debug file" msgstr "结束清理调试文件" #: apps/common/result/api.py:17 apps/common/result/api.py:27 msgid "response code" msgstr "响应码" #: apps/common/result/api.py:18 apps/common/result/api.py:19 #: apps/common/result/api.py:28 apps/common/result/api.py:29 msgid "error prompt" msgstr "错误提示" #: apps/common/result/api.py:43 msgid "total number of data" msgstr "总数据" #: apps/common/result/api.py:44 msgid "current page" msgstr "当前页" #: apps/common/result/api.py:45 msgid "page size" msgstr "每页大小" #: apps/common/result/result.py:31 #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Success" msgstr "成功" #: apps/common/utils/common.py:91 msgid "Text-to-speech node, the text content must be of string type" msgstr "文本转语音节点,文本内容必须是字符串类型" #: apps/common/utils/common.py:93 msgid "Text-to-speech node, the text content cannot be empty" msgstr "文本转语音节点,文本内容不能为空" #: apps/common/utils/common.py:246 #, python-brace-format msgid "Limit {count} exceeded, please contact us (https://fit2cloud.com/)." msgstr "超过限制 {count},请联系我们 (https://fit2cloud.com/)." #: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17 #: apps/folders/serializers/folder.py:98 msgid "folder name" msgstr "文件夹名称" #: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19 #: apps/folders/serializers/folder.py:99 msgid "folder description" msgstr "文件夹描述" #: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23 #: apps/folders/serializers/folder.py:102 msgid "parent id" msgstr "父级 ID" #: apps/folders/serializers/folder.py:75 msgid "Folder depth cannot exceed 5 levels" msgstr "文件夹深度不能超过5级" #: apps/folders/serializers/folder.py:100 msgid "folder user id" msgstr "文件夹用户 ID" #: apps/folders/serializers/folder.py:105 #: apps/knowledge/serializers/knowledge.py:112 #: apps/knowledge/serializers/knowledge.py:207 #: apps/knowledge/serializers/knowledge.py:447 #: apps/knowledge/serializers/knowledge.py:559 #: apps/knowledge/serializers/knowledge.py:637 #: apps/models_provider/serializers/model_serializer.py:108 #: apps/models_provider/serializers/model_serializer.py:212 #: apps/models_provider/serializers/model_serializer.py:252 #: apps/shared/serializers/shared_knowledge.py:107 #: apps/shared/serializers/shared_knowledge.py:156 #: apps/shared/serializers/shared_tool.py:84 #: apps/system_manage/serializers/user_resource_permission.py:75 #: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209 #: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664 #: apps/xpack/serializers/dataset_lark_serializer.py:46 #: apps/xpack/serializers/dataset_lark_serializer.py:285 #: apps/xpack/serializers/system_api_key.py:23 msgid "user id" msgstr "用户ID" #: apps/folders/serializers/folder.py:123 msgid "Folder name already exists" msgstr "文件夹名称已存在" #: apps/folders/serializers/folder.py:150 #: apps/folders/serializers/folder.py:182 msgid "Folder does not exist" msgstr "文件夹不存在" #: apps/folders/serializers/folder.py:184 msgid "Cannot delete root folder" msgstr "无法删除根文件夹" #: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32 #: apps/folders/views/folder.py:33 msgid "Create folder" msgstr "创建文件夹" #: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63 #: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110 #: apps/folders/views/folder.py:129 msgid "Folder" msgstr "文件夹" #: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59 #: apps/folders/views/folder.py:60 msgid "Get folder tree" msgstr "获取文件夹树" #: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81 #: apps/folders/views/folder.py:82 msgid "Update folder" msgstr "更新文件夹" #: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106 #: apps/folders/views/folder.py:107 msgid "Get folder" msgstr "获取文件夹" #: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125 #: apps/folders/views/folder.py:126 msgid "Delete folder" msgstr "删除文件夹" #: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52 #: apps/knowledge/serializers/problem.py:40 msgid "problem list" msgstr "问题列表" #: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53 #: apps/knowledge/serializers/problem.py:41 msgid "problem" msgstr "问题 ID" #: apps/knowledge/serializers/common.py:32 #: apps/knowledge/serializers/knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:29 msgid "source url" msgstr "来源" #: apps/knowledge/serializers/common.py:33 #: apps/knowledge/serializers/document.py:152 msgid "selector" msgstr "选择器" #: apps/knowledge/serializers/common.py:40 #, python-brace-format msgid "URL error, cannot parse [{source_url}]" msgstr "URL 错误,无法解析 [{source_url}]" #: apps/knowledge/serializers/common.py:48 #: apps/knowledge/serializers/document.py:78 #: apps/knowledge/serializers/document.py:170 #: apps/knowledge/serializers/document.py:186 msgid "id list" msgstr "ID 列表" #: apps/knowledge/serializers/common.py:58 #, python-brace-format msgid "The following id does not exist: {error_id_list}" msgstr "以下ID不存在: {error_id_list}" #: apps/knowledge/serializers/common.py:74 #: apps/knowledge/serializers/document.py:166 #: apps/knowledge/serializers/document.py:171 #: apps/knowledge/serializers/document.py:178 msgid "state list" msgstr "状态列表" #: apps/knowledge/serializers/common.py:117 #: apps/knowledge/serializers/common.py:141 msgid "The knowledge base is inconsistent with the vector model" msgstr "知识库与向量模型不一致" #: apps/knowledge/serializers/common.py:119 #: apps/knowledge/serializers/common.py:143 msgid "Knowledge base setting error, please reset the knowledge base" msgstr "知识库设置错误,请重置知识库" #: apps/knowledge/serializers/document.py:79 #: apps/knowledge/serializers/document.py:97 #: apps/knowledge/serializers/document.py:353 msgid "task type" msgstr "任务类型" #: apps/knowledge/serializers/document.py:87 #: apps/knowledge/serializers/document.py:105 msgid "task type not support" msgstr "任务类型不支持" #: apps/knowledge/serializers/document.py:91 #: apps/knowledge/serializers/document.py:110 #: apps/knowledge/serializers/document.py:350 msgid "document name" msgstr "文档名称" #: apps/knowledge/serializers/document.py:93 msgid "source file id" msgstr "源文件 ID" #: apps/knowledge/serializers/document.py:113 #: apps/knowledge/serializers/document.py:194 msgid "The type only supports optimization|directly_return" msgstr "该类型仅支持优化|直接返回" #: apps/knowledge/serializers/document.py:115 #: apps/knowledge/serializers/document.py:187 #: apps/knowledge/serializers/document.py:351 msgid "hit handling method" msgstr "命中处理方法" #: apps/knowledge/serializers/document.py:118 #: apps/knowledge/serializers/document.py:189 msgid "directly return similarity" msgstr "直接返回相似度" #: apps/knowledge/serializers/document.py:120 #: apps/knowledge/serializers/document.py:352 msgid "document is active" msgstr "文档已激活" #: apps/knowledge/serializers/document.py:139 #: apps/knowledge/serializers/document.py:156 #: apps/knowledge/serializers/document.py:161 msgid "file list" msgstr "文件 列表" #: apps/knowledge/serializers/document.py:140 msgid "limit" msgstr "限制" #: apps/knowledge/serializers/document.py:143 #: apps/knowledge/serializers/document.py:144 msgid "patterns" msgstr "分割符" #: apps/knowledge/serializers/document.py:146 msgid "Auto Clean" msgstr "自动清理" #: apps/knowledge/serializers/document.py:150 #: apps/knowledge/serializers/document.py:151 msgid "document url list" msgstr "文档 URL 列表" #: apps/knowledge/serializers/document.py:175 #: apps/knowledge/serializers/document.py:182 msgid "document id list" msgstr "文档 ID 列表" #: apps/knowledge/serializers/document.py:176 #: apps/knowledge/serializers/paragraph.py:58 #: apps/models_provider/api/model.py:105 #: apps/models_provider/serializers/model_apply_serializers.py:51 #: apps/models_provider/serializers/model_serializer.py:107 #: apps/models_provider/serializers/model_serializer.py:364 #: apps/shared/api/shared_model.py:61 #: apps/shared/serializers/shared_model.py:54 msgid "model id" msgstr "模型ID" #: apps/knowledge/serializers/document.py:177 #: apps/knowledge/serializers/paragraph.py:59 msgid "prompt" msgstr "提示词" #: apps/knowledge/serializers/document.py:201 msgid "The template type only supports excel|csv" msgstr "模板类型仅支持 excel|csv" #: apps/knowledge/serializers/document.py:254 #: apps/knowledge/serializers/document.py:348 #: apps/knowledge/serializers/document.py:409 #: apps/knowledge/serializers/document.py:504 #: apps/knowledge/serializers/document.py:889 #: apps/knowledge/serializers/document.py:964 #: apps/knowledge/serializers/document.py:984 #: apps/knowledge/serializers/document.py:1167 #: apps/knowledge/serializers/knowledge.py:209 #: apps/knowledge/serializers/knowledge.py:558 #: apps/knowledge/serializers/paragraph.py:70 #: apps/knowledge/serializers/paragraph.py:138 #: apps/knowledge/serializers/paragraph.py:239 #: apps/knowledge/serializers/paragraph.py:321 #: apps/knowledge/serializers/paragraph.py:347 #: apps/knowledge/serializers/paragraph.py:398 #: apps/knowledge/serializers/paragraph.py:439 #: apps/knowledge/serializers/paragraph.py:559 #: apps/knowledge/serializers/problem.py:62 #: apps/knowledge/serializers/problem.py:126 #: apps/knowledge/serializers/problem.py:177 #: apps/knowledge/serializers/problem.py:205 #: apps/shared/api/shared_knowledge.py:196 #: apps/shared/api/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:158 #: apps/shared/serializers/shared_knowledge.py:205 #: apps/xpack/serializers/dataset_lark_serializer.py:104 #: apps/xpack/serializers/dataset_lark_serializer.py:263 #: apps/xpack/serializers/dataset_lark_serializer.py:284 msgid "knowledge id" msgstr "知识库 ID" #: apps/knowledge/serializers/document.py:255 #: apps/knowledge/serializers/paragraph.py:441 msgid "target knowledge id" msgstr "当前知识库 ID" #: apps/knowledge/serializers/document.py:256 msgid "document list" msgstr "文档列表" #: apps/knowledge/serializers/document.py:257 #: apps/knowledge/serializers/document.py:410 #: apps/knowledge/serializers/document.py:503 #: apps/knowledge/serializers/document.py:737 #: apps/knowledge/serializers/paragraph.py:61 #: apps/knowledge/serializers/paragraph.py:71 #: apps/knowledge/serializers/paragraph.py:140 #: apps/knowledge/serializers/paragraph.py:240 #: apps/knowledge/serializers/paragraph.py:322 #: apps/knowledge/serializers/paragraph.py:349 #: apps/knowledge/serializers/paragraph.py:399 #: apps/knowledge/serializers/paragraph.py:440 #: apps/knowledge/serializers/paragraph.py:560 #: apps/knowledge/serializers/problem.py:36 #: apps/knowledge/serializers/problem.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:160 msgid "document id" msgstr "文档 ID" #: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25 #: apps/xpack/serializers/operate_log_serializer.py:60 #: apps/xpack/serializers/operate_log_serializer.py:174 msgid "status" msgstr "状态" #: apps/knowledge/serializers/document.py:355 msgid "order by" msgstr "排序" #: apps/knowledge/serializers/document.py:417 #: apps/knowledge/serializers/document.py:510 #: apps/xpack/serializers/dataset_lark_serializer.py:167 #: apps/xpack/serializers/dataset_lark_serializer.py:189 msgid "document id not exist" msgstr "文档 ID 不存在" #: apps/knowledge/serializers/document.py:419 #: apps/knowledge/serializers/knowledge.py:570 msgid "Synchronization is only supported for web site types" msgstr "仅支持网站类型的同步" #: apps/knowledge/serializers/document.py:661 msgid "The task is being executed, please do not send it repeatedly." msgstr "任务正在执行,请勿重复发送。" #: apps/knowledge/serializers/document.py:674 msgid "Section title (optional)" msgstr "章节标题" #: apps/knowledge/serializers/document.py:675 msgid "" "Section content (required, question answer, no more than 4096 characters)" msgstr "章节内容(必填,问答,不超过4096个字符)" #: apps/knowledge/serializers/document.py:676 msgid "Question (optional, one per line in the cell)" msgstr "问题(可选,每个单元格一行)" #: apps/knowledge/serializers/document.py:742 msgid "knowledge id not exist" msgstr "知识库 ID 不存在" #: apps/knowledge/serializers/document.py:898 msgid "The maximum size of the uploaded file cannot exceed {}MB" msgstr "上传文件的最大大小不能超过 {}MB" #: apps/knowledge/serializers/document.py:976 msgid "space" msgstr "空格" #: apps/knowledge/serializers/document.py:977 msgid "semicolon" msgstr "分号" #: apps/knowledge/serializers/document.py:977 msgid "comma" msgstr "逗号" #: apps/knowledge/serializers/document.py:978 msgid "period" msgstr "句号" #: apps/knowledge/serializers/document.py:978 msgid "enter" msgstr "回车" #: apps/knowledge/serializers/document.py:979 msgid "blank line" msgstr "空行" #: apps/knowledge/serializers/document.py:1140 msgid "Hit handling method is required" msgstr "命中处理方法是必需的" #: apps/knowledge/serializers/document.py:1142 msgid "The hit processing method must be directly_return|optimization" msgstr "命中处理方法必须是直接返回|优化" #: apps/knowledge/serializers/knowledge.py:51 #: apps/knowledge/serializers/knowledge.py:58 #: apps/knowledge/serializers/knowledge.py:67 #: apps/knowledge/serializers/knowledge.py:108 #: apps/shared/api/shared_knowledge.py:117 #: apps/shared/api/shared_knowledge.py:150 #: apps/shared/serializers/shared_knowledge.py:20 #: apps/shared/serializers/shared_knowledge.py:26 #: apps/shared/serializers/shared_knowledge.py:59 #: apps/shared/serializers/shared_knowledge.py:106 #: apps/xpack/serializers/dataset_lark_serializer.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:289 msgid "knowledge name" msgstr "知识库名称" #: apps/knowledge/serializers/knowledge.py:53 #: apps/knowledge/serializers/knowledge.py:60 #: apps/knowledge/serializers/knowledge.py:68 #: apps/knowledge/serializers/knowledge.py:110 #: apps/shared/api/shared_knowledge.py:124 #: apps/shared/api/shared_knowledge.py:157 #: apps/shared/serializers/shared_knowledge.py:21 #: apps/shared/serializers/shared_knowledge.py:27 #: apps/shared/serializers/shared_knowledge.py:60 #: apps/shared/serializers/shared_knowledge.py:108 #: apps/xpack/serializers/dataset_lark_serializer.py:53 #: apps/xpack/serializers/dataset_lark_serializer.py:291 msgid "knowledge description" msgstr "知识库描述" #: apps/knowledge/serializers/knowledge.py:54 #: apps/knowledge/serializers/knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:22 #: apps/shared/serializers/shared_knowledge.py:28 msgid "knowledge embedding" msgstr "知识库向量" #: apps/knowledge/serializers/knowledge.py:63 #: apps/shared/serializers/shared_knowledge.py:30 msgid "knowledge selector" msgstr "知识库选择器" #: apps/knowledge/serializers/knowledge.py:73 #: apps/xpack/serializers/dataset_lark_serializer.py:296 msgid "application id list" msgstr "智能体 ID 列表" #: apps/knowledge/serializers/knowledge.py:75 msgid "file size limit" msgstr "文件大小限制" #: apps/knowledge/serializers/knowledge.py:76 msgid "file count limit" msgstr "文件数量限制" #: apps/knowledge/serializers/knowledge.py:95 #: apps/knowledge/serializers/knowledge.py:638 msgid "query text" msgstr "查询文本" #: apps/knowledge/serializers/knowledge.py:96 #: apps/knowledge/serializers/knowledge.py:639 msgid "top number" msgstr "Top 数量" #: apps/knowledge/serializers/knowledge.py:98 #: apps/knowledge/serializers/knowledge.py:641 msgid "search mode" msgstr "搜索模式" #: apps/knowledge/serializers/knowledge.py:113 msgid "knowledge scope" msgstr "知识库范围" #: apps/knowledge/serializers/knowledge.py:169 #: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464 msgid "Folder not found" msgstr "文件夹不存在" #: apps/knowledge/serializers/knowledge.py:236 #: apps/knowledge/serializers/knowledge.py:265 msgid "Failed to send the vectorization task, please try again later!" msgstr "发送向量化任务失败,请稍后再试!" #: apps/knowledge/serializers/knowledge.py:315 #: apps/knowledge/serializers/knowledge.py:471 #: apps/knowledge/serializers/knowledge.py:533 #: apps/xpack/serializers/dataset_lark_serializer.py:82 #: apps/xpack/serializers/dataset_lark_serializer.py:340 msgid "Knowledge base name duplicate!" msgstr "知识库名称重复!" #: apps/knowledge/serializers/knowledge.py:341 #: apps/xpack/serializers/dataset_lark_serializer.py:359 #, python-brace-format msgid "Unknown application id {knowledge_id}, cannot be associated" msgstr "未知智能体 ID {knowledge_id},无法关联" #: apps/knowledge/serializers/knowledge.py:449 #: apps/shared/serializers/shared_knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:110 #: apps/shared/serializers/shared_tool.py:46 #: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456 #: apps/xpack/serializers/dataset_lark_serializer.py:47 msgid "scope" msgstr "范围" #: apps/knowledge/serializers/knowledge.py:460 msgid "" "The community version supports up to 50 knowledge bases. If you need more " "knowledge bases, please contact us (https://fit2cloud.com/)." msgstr "" "社区版支持最多50个知识库,如需更多知识库,请联系我们 (https://" "fit2cloud.com/)." #: apps/knowledge/serializers/knowledge.py:560 msgid "sync type" msgstr "同步类型" #: apps/knowledge/serializers/knowledge.py:562 msgid "The synchronization type only supports:replace|complete" msgstr "同步类型仅支持:replace|complete" #: apps/knowledge/serializers/knowledge.py:568 #: apps/knowledge/serializers/knowledge.py:649 msgid "id does not exist" msgstr "知识库 ID 不存在" #: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76 msgid "id" msgstr "ID" #: apps/knowledge/serializers/paragraph.py:39 #: apps/knowledge/serializers/problem.py:27 #: apps/knowledge/serializers/problem.py:31 #: apps/knowledge/serializers/problem.py:206 msgid "content" msgstr "内容" #: apps/knowledge/serializers/paragraph.py:41 #: apps/knowledge/serializers/paragraph.py:48 #: apps/knowledge/serializers/paragraph.py:51 #: apps/knowledge/serializers/paragraph.py:65 #: apps/knowledge/serializers/paragraph.py:67 #: apps/knowledge/serializers/paragraph.py:323 msgid "section title" msgstr "章节标题" #: apps/knowledge/serializers/paragraph.py:44 #: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164 #: apps/xpack/serializers/system_api_key.py:11 msgid "Is active" msgstr "是否启用" #: apps/knowledge/serializers/paragraph.py:56 #: apps/knowledge/serializers/paragraph.py:443 msgid "paragraph id list" msgstr "段落 ID 列表" #: apps/knowledge/serializers/paragraph.py:57 #: apps/knowledge/serializers/paragraph.py:72 #: apps/knowledge/serializers/paragraph.py:136 #: apps/knowledge/serializers/paragraph.py:350 #: apps/knowledge/serializers/paragraph.py:444 #: apps/knowledge/serializers/paragraph.py:561 #: apps/knowledge/serializers/problem.py:35 #: apps/knowledge/serializers/problem.py:50 msgid "paragraph id" msgstr "段落 ID" #: apps/knowledge/serializers/paragraph.py:77 #: apps/knowledge/serializers/paragraph.py:145 msgid "Paragraph id does not exist" msgstr "段落 ID 不存在" #: apps/knowledge/serializers/paragraph.py:108 msgid "Already associated, please do not associate again" msgstr "已关联,请勿再次关联" #: apps/knowledge/serializers/paragraph.py:181 msgid "Problem id does not exist" msgstr "问题 ID 不存在" #: apps/knowledge/serializers/paragraph.py:348 #: apps/knowledge/serializers/problem.py:26 #: apps/knowledge/serializers/problem.py:46 #: apps/knowledge/serializers/problem.py:56 #: apps/knowledge/serializers/problem.py:127 msgid "problem id" msgstr "问题 ID" #: apps/knowledge/serializers/paragraph.py:358 msgid "Paragraph does not exist" msgstr "段落不存在" #: apps/knowledge/serializers/paragraph.py:360 msgid "Problem does not exist" msgstr "问题不存在" #: apps/knowledge/serializers/paragraph.py:435 msgid "The task is being executed, please do not send it again." msgstr "任务正在执行,请勿重复发送。" #: apps/knowledge/serializers/paragraph.py:442 msgid "target document id" msgstr "目标文档 ID" #: apps/knowledge/serializers/paragraph.py:453 msgid "The document to be migrated is consistent with the target document" msgstr "迁移的文档与目标文档一致" #: apps/knowledge/serializers/paragraph.py:455 msgid "The document id does not exist [{document_id}]" msgstr "以下文档ID不存在: {error_id_list}" #: apps/knowledge/serializers/paragraph.py:459 msgid "The target document id does not exist [{document_id}]" msgstr "以下目标文档ID不存在: {error_id_list}" #: apps/knowledge/serializers/paragraph.py:573 msgid "new_position must be an integer" msgstr "new_position 必须是整数" #: apps/knowledge/serializers/problem.py:45 #: apps/knowledge/serializers/problem.py:55 msgid "problem id list" msgstr "问题 ID 列表" #: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74 #, python-brace-format msgid "Failed to obtain vector model: {error} {traceback}" msgstr "向量模型获取失败: {error} {traceback}" #: apps/knowledge/task/embedding.py:103 #, python-brace-format msgid "Start--->Vectorized knowledge: {knowledge_id}" msgstr "开始--->向量知识库: {knowledge_id}" #: apps/knowledge/task/embedding.py:107 #, python-brace-format msgid "Knowledge documentation: {document_names}" msgstr "知识库文档: {document_names}" #: apps/knowledge/task/embedding.py:120 #, python-brace-format msgid "End--->Vectorized knowledge: {knowledge_id}" msgstr "结束--->向量知识库: {knowledge_id}" #: apps/knowledge/task/generate.py:106 #, python-brace-format msgid "" "Generate issue based on document: {document_id} error {error}{traceback}" msgstr "生成问题基于文档: {document_id} 错误 {error}{traceback}" #: apps/knowledge/task/generate.py:110 #, python-brace-format msgid "End--->Generate problem: {document_id}" msgstr "结束--->生成问题: {document_id}" #: apps/knowledge/task/handler.py:121 #, python-brace-format msgid "Association problem failed {error}" msgstr "关联问题失败 {error}" #: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47 #, python-brace-format msgid "Start--->Start synchronization web knowledge base:{knowledge_id}" msgstr "开始--->开始同步 web 知识库:{knowledge_id}" #: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51 #, python-brace-format msgid "End--->End synchronization web knowledge base:{knowledge_id}" msgstr "结束--->结束同步 web 知识库:{knowledge_id}" #: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53 #, python-brace-format msgid "Synchronize web knowledge base:{knowledge_id} error{error}{traceback}" msgstr "同步 web 知识库:{knowledge_id} 错误{error}{traceback}" #: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29 #: apps/knowledge/views/document.py:30 msgid "Create document" msgstr "创建文档" #: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57 #: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104 #: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160 #: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209 #: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267 #: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323 #: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382 #: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441 #: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501 #: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553 #: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599 #: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664 #: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723 #: apps/knowledge/views/document.py:737 #: apps/xpack/views/dataset_lark_views.py:72 #: apps/xpack/views/dataset_lark_views.py:91 #: apps/xpack/views/dataset_lark_views.py:111 #: apps/xpack/views/dataset_lark_views.py:132 msgid "Knowledge Base/Documentation" msgstr "知识库/文档" #: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53 #: apps/knowledge/views/document.py:54 msgid "Get document" msgstr "获取文档" #: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80 #: apps/knowledge/views/document.py:81 msgid "Get document details" msgstr "文档文档详情" #: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99 #: apps/knowledge/views/document.py:100 msgid "Modify document" msgstr "修改文档" #: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124 #: apps/knowledge/views/document.py:125 msgid "Delete document" msgstr "删除文档" #: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155 #: apps/knowledge/views/document.py:156 msgid "Segmented document" msgstr "分段文档" #: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187 #: apps/knowledge/views/document.py:188 msgid "Get a list of segment IDs" msgstr "获取分段列表" #: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204 #: apps/knowledge/views/document.py:205 msgid "Modify document hit processing methods in batches" msgstr "批量修改文档命中处理方法" #: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233 #: apps/knowledge/views/document.py:234 msgid "Synchronize web site types" msgstr "同步网站类型" #: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262 #: apps/knowledge/views/document.py:263 msgid "Refresh document vector library" msgstr "刷新文档向量库" #: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290 #: apps/knowledge/views/document.py:291 msgid "Cancel task" msgstr "取消任务" #: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318 #: apps/knowledge/views/document.py:319 msgid "Cancel tasks in batches" msgstr "批量取消任务" #: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347 #: apps/knowledge/views/document.py:348 msgid "Create documents in batches" msgstr "批量创建文档" #: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377 #: apps/knowledge/views/document.py:378 msgid "Batch sync documents" msgstr "批量同步文档" #: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407 #: apps/knowledge/views/document.py:408 msgid "Delete documents in batches" msgstr "批量删除文档" #: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437 msgid "Batch refresh document vector library" msgstr "批量刷新文档向量库" #: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467 #: apps/knowledge/views/document.py:468 msgid "Batch generate related problems" msgstr "批量生成相关问题" #: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497 #: apps/knowledge/views/document.py:498 msgid "Get document by pagination" msgstr "分页获取文档" #: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524 msgid "Export document" msgstr "导出文档" #: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550 msgid "Export Zip document" msgstr "导出 Zip 文档" #: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576 msgid "Download source file" msgstr "下载源文件" #: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595 msgid "Migrate documents in batches" msgstr "批量迁移文档" #: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628 #: apps/knowledge/views/document.py:629 #: apps/shared/views/shared_document.py:570 msgid "Create Web site documents" msgstr "创建网站文档" #: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659 #: apps/knowledge/views/document.py:660 msgid "Import QA and create documentation" msgstr "导入问答并创建文档" #: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690 #: apps/knowledge/views/document.py:691 msgid "Import tables and create documents" msgstr "导入表格并创建文档" #: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720 msgid "Get QA template" msgstr "获取问答模板" #: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734 msgid "Get form template" msgstr "获取表格模板" #: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26 #: apps/knowledge/views/knowledge.py:27 msgid "Get knowledge by folder" msgstr "根据文件夹获取知识库" #: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59 #: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106 #: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156 #: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218 #: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266 #: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319 #: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369 #: apps/knowledge/views/knowledge.py:397 #: apps/xpack/views/dataset_lark_views.py:29 #: apps/xpack/views/dataset_lark_views.py:50 msgid "Knowledge Base" msgstr "知识库" #: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54 #: apps/knowledge/views/knowledge.py:55 msgid "Edit knowledge" msgstr "修改知识库" #: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78 #: apps/knowledge/views/knowledge.py:79 msgid "Delete knowledge" msgstr "删除知识库" #: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102 #: apps/knowledge/views/knowledge.py:103 msgid "Get knowledge" msgstr "获取知识库" #: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123 #: apps/knowledge/views/knowledge.py:124 msgid "Get the knowledge base paginated list" msgstr "获取知识库分页列表" #: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151 #: apps/knowledge/views/knowledge.py:152 msgid "Synchronize the knowledge base of the website" msgstr "同步网站知识库" #: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183 #: apps/knowledge/views/knowledge.py:184 msgid "Hit test list" msgstr "命中测试列表" #: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213 #: apps/knowledge/views/knowledge.py:214 msgid "Re-vectorize" msgstr "重新向量化" #: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239 msgid "Export knowledge base" msgstr "导出知识库" #: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263 msgid "Export knowledge base containing images" msgstr "导出包含图片的知识库" #: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288 #: apps/knowledge/views/knowledge.py:289 msgid "Generate related" msgstr "生成相关" #: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315 #: apps/knowledge/views/knowledge.py:316 msgid "Get model for knowledge base" msgstr "获取知识库模型" #: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339 #: apps/knowledge/views/knowledge.py:340 msgid "Get embedding model for knowledge base" msgstr "获取知识库向量模型" #: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364 #: apps/knowledge/views/knowledge.py:365 msgid "Create base knowledge" msgstr "创建知识库" #: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392 #: apps/knowledge/views/knowledge.py:393 msgid "Create web knowledge" msgstr "创建 web 知识库" #: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25 #: apps/knowledge/views/paragraph.py:26 msgid "Paragraph list" msgstr "段落列表" #: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53 #: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102 #: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167 #: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224 #: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289 #: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351 #: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415 msgid "Knowledge Base/Documentation/Paragraph" msgstr "知识库/文档/段落" #: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49 msgid "Create Paragraph" msgstr "创建段落" #: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77 #: apps/knowledge/views/paragraph.py:78 msgid "Batch Paragraph" msgstr "批量段落" #: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98 msgid "Migrate paragraphs in batches" msgstr "批量迁移段落" #: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133 #: apps/knowledge/views/paragraph.py:134 msgid "Batch Generate Related" msgstr "批量生成相关" #: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162 #: apps/knowledge/views/paragraph.py:163 msgid "Modify paragraph data" msgstr "修改段落数据" #: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195 #: apps/knowledge/views/paragraph.py:196 msgid "Get paragraph details" msgstr "获取段落详情" #: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220 #: apps/knowledge/views/paragraph.py:221 msgid "Delete paragraph" msgstr "删除段落" #: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254 #: apps/knowledge/views/paragraph.py:255 msgid "Add associated questions" msgstr "添加关联问题" #: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285 #: apps/knowledge/views/paragraph.py:286 msgid "Get a list of paragraph questions" msgstr "获取段落问题列表" #: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311 #: apps/knowledge/views/paragraph.py:312 msgid "Disassociation issue" msgstr "取消关联问题" #: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346 #: apps/knowledge/views/paragraph.py:347 msgid "Related questions" msgstr "关联问题" #: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381 #: apps/knowledge/views/paragraph.py:382 msgid "Get paragraph list by pagination" msgstr "获取段落列表" #: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410 #: apps/knowledge/views/paragraph.py:411 #: apps/resource_manage/views/paragraph.py:364 #: apps/resource_manage/views/paragraph.py:365 #: apps/resource_manage/views/paragraph.py:366 #: apps/shared/views/shared_paragraph.py:365 #: apps/shared/views/shared_paragraph.py:366 #: apps/shared/views/shared_paragraph.py:367 msgid "Adjust paragraph position" msgstr "调整段落位置" #: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24 #: apps/knowledge/views/problem.py:25 msgid "Question list" msgstr "问题列表" #: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53 #: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104 #: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157 #: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216 msgid "Knowledge Base/Documentation/Paragraph/Question" msgstr "知识库/文档/段落/问题" #: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48 #: apps/knowledge/views/problem.py:49 msgid "Create question" msgstr "创建问题" #: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74 #: apps/knowledge/views/problem.py:75 msgid "Get a list of associated paragraphs" msgstr "获取关联段落列表" #: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99 #: apps/knowledge/views/problem.py:100 msgid "Batch associated paragraphs" msgstr "批量关联段落" #: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126 #: apps/knowledge/views/problem.py:127 msgid "Batch deletion issues" msgstr "批量删除问题" #: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153 #: apps/knowledge/views/problem.py:154 msgid "Delete question" msgstr "删除问题" #: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181 #: apps/knowledge/views/problem.py:182 msgid "Modify question" msgstr "修改问题" #: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212 #: apps/knowledge/views/problem.py:213 msgid "Get the list of questions by page" msgstr "分页获取问题列表" #: apps/maxkb/settings/base.py:101 msgid "Intelligent customer service platform" msgstr "强大易用的企业级智能体平台" #: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17 #: apps/models_provider/api/provide.py:23 #: apps/models_provider/api/provide.py:28 #: apps/models_provider/api/provide.py:30 #: apps/models_provider/api/provide.py:82 #: apps/models_provider/serializers/model_serializer.py:40 #: apps/models_provider/serializers/model_serializer.py:215 #: apps/models_provider/serializers/model_serializer.py:253 #: apps/models_provider/serializers/model_serializer.py:318 #: apps/models_provider/serializers/model_serializer.py:393 #: apps/shared/api/shared_model.py:18 #: apps/shared/serializers/shared_model.py:111 msgid "model name" msgstr "模型名称" #: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29 #: apps/models_provider/api/provide.py:70 #: apps/models_provider/api/provide.py:98 #: apps/models_provider/serializers/model_serializer.py:42 #: apps/models_provider/serializers/model_serializer.py:217 #: apps/models_provider/serializers/model_serializer.py:255 #: apps/models_provider/serializers/model_serializer.py:319 #: apps/models_provider/serializers/model_serializer.py:394 #: apps/shared/api/shared_model.py:25 #: apps/shared/serializers/shared_model.py:112 msgid "model type" msgstr "模型类型" #: apps/models_provider/api/model.py:51 #: apps/models_provider/serializers/model_serializer.py:43 #: apps/models_provider/serializers/model_serializer.py:219 #: apps/models_provider/serializers/model_serializer.py:256 #: apps/models_provider/serializers/model_serializer.py:320 #: apps/models_provider/serializers/model_serializer.py:395 #: apps/shared/api/shared_model.py:32 #: apps/shared/serializers/shared_model.py:113 msgid "base model" msgstr "基础模型" #: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18 #: apps/models_provider/api/provide.py:38 #: apps/models_provider/api/provide.py:76 #: apps/models_provider/api/provide.py:104 #: apps/models_provider/api/provide.py:126 #: apps/models_provider/serializers/model_serializer.py:41 #: apps/models_provider/serializers/model_serializer.py:254 #: apps/models_provider/serializers/model_serializer.py:321 #: apps/models_provider/serializers/model_serializer.py:396 #: apps/shared/api/shared_model.py:39 #: apps/shared/serializers/shared_model.py:114 msgid "provider" msgstr "供应商" #: apps/models_provider/api/model.py:65 #: apps/models_provider/serializers/model_serializer.py:322 #: apps/models_provider/serializers/model_serializer.py:397 #: apps/shared/api/shared_model.py:46 #: apps/shared/serializers/shared_model.py:115 msgid "create user" msgstr "创建用户" #: apps/models_provider/api/provide.py:19 #: apps/xpack/serializers/application_setting_serializer.py:41 #: apps/xpack/serializers/system_params.py:21 msgid "icon" msgstr "图标" #: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134 msgid "input type" msgstr "输入类型" #: apps/models_provider/api/provide.py:35 msgid "label" msgstr "标签" #: apps/models_provider/api/provide.py:36 msgid "text field" msgstr "文本字段" #: apps/models_provider/api/provide.py:37 msgid "value field" msgstr "值" #: apps/models_provider/api/provide.py:39 msgid "method" msgstr "方法" #: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119 #: apps/tools/serializers/tool.py:133 msgid "required" msgstr "必填" #: apps/models_provider/api/provide.py:41 msgid "default value" msgstr "默认值" #: apps/models_provider/api/provide.py:42 msgid "relation show field dict" msgstr "关系显示字段" #: apps/models_provider/api/provide.py:43 msgid "relation trigger field dict" msgstr "关系触发字段" #: apps/models_provider/api/provide.py:44 msgid "trigger type" msgstr "触发类型" #: apps/models_provider/api/provide.py:45 msgid "attrs" msgstr "属性" #: apps/models_provider/api/provide.py:46 msgid "props info" msgstr "props 信息" #: apps/models_provider/base_model_provider.py:60 msgid "Model type cannot be empty" msgstr "模型类型不能为空" #: apps/models_provider/base_model_provider.py:85 msgid "The current platform does not support downloading models" msgstr "当前平台不支持下载模型" #: apps/models_provider/base_model_provider.py:143 msgid "LLM" msgstr "大语言模型" #: apps/models_provider/base_model_provider.py:144 msgid "Embedding Model" msgstr "向量模型" #: apps/models_provider/base_model_provider.py:145 msgid "Speech2Text" msgstr "语音识别" #: apps/models_provider/base_model_provider.py:146 msgid "TTS" msgstr "语音合成" #: apps/models_provider/base_model_provider.py:147 msgid "Vision Model" msgstr "视觉模型" #: apps/models_provider/base_model_provider.py:148 msgid "Image Generation" msgstr "图片生成" #: apps/models_provider/base_model_provider.py:149 msgid "Rerank" msgstr "重排模型" #: apps/models_provider/base_model_provider.py:223 msgid "The model does not support" msgstr "模型不支持" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42 msgid "" "With the GTE-Rerank text sorting series model developed by Alibaba Tongyi " "Lab, developers can integrate high-quality text retrieval and sorting " "through the LlamaIndex framework." msgstr "" "阿里巴巴通义实验室开发的GTE-Rerank文本排序系列模型,开发者可以通过LlamaIndex" "框架进行集成高质量文本检索、排序。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45 msgid "" "Chinese (including various dialects such as Cantonese), English, Japanese, " "and Korean support free switching between multiple languages." msgstr "中文(含粤语等各种方言)、英文、日语、韩语支持多个语种自由切换" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48 msgid "" "CosyVoice is based on a new generation of large generative speech models, " "which can predict emotions, intonation, rhythm, etc. based on context, and " "has better anthropomorphic effects." msgstr "" "CosyVoice基于新一代生成式语音大模型,能根据上下文预测情绪、语调、韵律等,具有" "更好的拟人效果" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51 msgid "" "Universal text vector is Tongyi Lab's multi-language text unified vector " "model based on the LLM base. It provides high-level vector services for " "multiple mainstream languages around the world and helps developers quickly " "convert text data into high-quality vector data." msgstr "" "通用文本向量,是通义实验室基于LLM底座的多语言文本统一向量模型,面向全球多个主" "流语种,提供高水准的向量服务,帮助开发者将文本数据快速转换为高质量的向量数" "据。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69 msgid "" "Tongyi Wanxiang - a large image model for text generation, supports " "bilingual input in Chinese and English, and supports the input of reference " "pictures for reference content or reference style migration. Key styles " "include but are not limited to watercolor, oil painting, Chinese painting, " "sketch, flat illustration, two-dimensional, and 3D. Cartoon." msgstr "" "通义万相-文本生成图像大模型,支持中英文双语输入,支持输入参考图片进行参考内容" "或者参考风格迁移,重点风格包括但不限于水彩、油画、中国画、素描、扁平插画、二" "次元、3D卡通。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95 msgid "Alibaba Cloud Bailian" msgstr "阿里云百炼" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/azure_model_provider/credential/image.py:40 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:69 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/model/stt.py:43 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/openai_model_provider/credential/image.py:35 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:59 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:37 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51 #: apps/models_provider/impl/tencent_model_provider/model/tti.py:54 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57 #: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:76 #: apps/models_provider/impl/xf_model_provider/model/tts.py:101 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:32 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34 #: apps/models_provider/impl/xinference_model_provider/model/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56 #: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49 msgid "Hello" msgstr "你好" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/azure_model_provider/credential/image.py:30 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:59 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:23 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:58 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:41 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:19 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/openai_model_provider/credential/image.py:25 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:48 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:22 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:61 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:40 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:28 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:22 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/xf_model_provider/credential/image.py:29 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:66 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:24 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:22 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40 #, python-brace-format msgid "{model_type} Model type is not supported" msgstr "{model_type} 模型类型不支持" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98 #, python-brace-format msgid "{key} is required" msgstr "{key} 是必填项" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61 #: apps/models_provider/impl/azure_model_provider/credential/image.py:50 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:40 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:77 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:58 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:42 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:45 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/openai_model_provider/credential/image.py:45 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:67 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:39 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:80 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:42 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/image.py:50 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:84 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:41 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:65 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59 #, python-brace-format msgid "" "Verification failed, please check whether the parameters are correct: {error}" msgstr "认证失败,请检查参数是否正确:{error}" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:23 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:23 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22 msgid "Temperature" msgstr "温度" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:24 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:24 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:42 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23 msgid "" "Higher values make the output more random, while lower values make it more " "focused and deterministic" msgstr "较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:32 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:43 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31 msgid "Output the maximum Tokens" msgstr "输出最大Token数" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31 msgid "Specify the maximum number of tokens that the model can generate." msgstr "指定模型可以生成的最大 tokens 数" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74 msgid "API URL" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:15 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15 msgid "Image size" msgstr "图片尺寸" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 msgid "Specify the size of the generated image, such as: 1024x1024" msgstr "指定生成图片的尺寸, 如: 1024x1024" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41 msgid "Number of pictures" msgstr "图片数量" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 msgid "Specify the number of generated images" msgstr "指定生成图片的数量" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Style" msgstr "风格" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Specify the style of generated images" msgstr "指定生成图片的风格" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48 msgid "Default value, the image style is randomly output by the model" msgstr "默认值,图片风格由模型随机输出" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49 msgid "photography" msgstr "摄影" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50 msgid "Portraits" msgstr "人像写真" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51 msgid "3D cartoon" msgstr "3D卡通" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52 msgid "animation" msgstr "动画" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53 msgid "painting" msgstr "油画" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54 msgid "watercolor" msgstr "水彩" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55 msgid "sketch" msgstr "素描" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56 msgid "Chinese painting" msgstr "中国画" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57 msgid "flat illustration" msgstr "扁平插画" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 msgid "Timbre" msgstr "音色" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 msgid "Chinese sounds can support mixed scenes of Chinese and English" msgstr "中文音色支持中英文混合场景" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26 msgid "Long Xiaochun" msgstr "龙小淳" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27 msgid "Long Xiaoxia" msgstr "龙小夏" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28 msgid "Long Xiaochen" msgstr "龙小诚" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29 msgid "Long Xiaobai" msgstr "龙小白" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30 msgid "Long Laotie" msgstr "龙老铁" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31 msgid "Long Shu" msgstr "龙书" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32 msgid "Long Shuo" msgstr "龙硕" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33 msgid "Long Jing" msgstr "龙婧" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34 msgid "Long Miao" msgstr "龙妙" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35 msgid "Long Yue" msgstr "龙悦" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36 msgid "Long Yuan" msgstr "龙媛" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37 msgid "Long Fei" msgstr "龙飞" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38 msgid "Long Jielidou" msgstr "龙杰力豆" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39 msgid "Long Tong" msgstr "龙彤" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40 msgid "Long Xiang" msgstr "龙祥" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "Speaking speed" msgstr "语速" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "[0.5, 2], the default is 1, usually one decimal place is enough" msgstr "[0.5,2],默认为1,通常一位小数就足够了" #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32 #: apps/models_provider/impl/azure_model_provider/credential/image.py:35 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:64 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:28 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:63 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:46 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:27 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/openai_model_provider/credential/image.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:53 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:27 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:66 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:27 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72 #: apps/models_provider/impl/xf_model_provider/credential/image.py:34 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:71 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:29 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:52 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:27 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45 #, python-brace-format msgid "{key} is required" msgstr "{key} 是必填项" #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:33 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:44 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:33 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:51 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32 msgid "Specify the maximum number of tokens that the model can generate" msgstr "指定模型可以生成的最大 tokens 数" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36 msgid "" "An update to Claude 2 that doubles the context window and improves " "reliability, hallucination rates, and evidence-based accuracy in long " "documents and RAG contexts." msgstr "" "Claude 2 的更新,采用双倍的上下文窗口,并在长文档和 RAG 上下文中提高可靠性、" "幻觉率和循证准确性。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43 msgid "" "Anthropic is a powerful model that can handle a variety of tasks, from " "complex dialogue and creative content generation to detailed command " "obedience." msgstr "" "Anthropic 功能强大的模型,可处理各种任务,从复杂的对话和创意内容生成到详细的" "指令服从。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50 msgid "" "The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-" "instant responsiveness. The model can answer simple queries and requests " "quickly. Customers will be able to build seamless AI experiences that mimic " "human interactions. Claude 3 Haiku can process images and return text " "output, and provides 200K context windows." msgstr "" "Claude 3 Haiku 是 Anthropic 最快速、最紧凑的模型,具有近乎即时的响应能力。该" "模型可以快速回答简单的查询和请求。客户将能够构建模仿人类交互的无缝人工智能体" "验。 Claude 3 Haiku 可以处理图像和返回文本输出,并且提供 200K 上下文窗口。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57 msgid "" "The Claude 3 Sonnet model from Anthropic strikes the ideal balance between " "intelligence and speed, especially when it comes to handling enterprise " "workloads. This model offers maximum utility while being priced lower than " "competing products, and it's been engineered to be a solid choice for " "deploying AI at scale." msgstr "" "Anthropic 推出的 Claude 3 Sonnet 模型在智能和速度之间取得理想的平衡,尤其是在" "处理企业工作负载方面。该模型提供最大的效用,同时价格低于竞争产品,并且其经过" "精心设计,是大规模部署人工智能的可靠选择。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64 msgid "" "The Claude 3.5 Sonnet raises the industry standard for intelligence, " "outperforming competing models and the Claude 3 Opus in extensive " "evaluations, with the speed and cost-effectiveness of our mid-range models." msgstr "" "Claude 3.5 Sonnet提高了智能的行业标准,在广泛的评估中超越了竞争对手的型号和" "Claude 3 Opus,具有我们中端型号的速度和成本效益。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71 msgid "" "A faster, more affordable but still very powerful model that can handle a " "range of tasks including casual conversation, text analysis, summarization " "and document question answering." msgstr "" "一种更快速、更实惠但仍然非常强大的模型,它可以处理一系列任务,包括随意对话、" "文本分析、摘要和文档问题回答。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78 msgid "" "Titan Text Premier is the most powerful and advanced model in the Titan Text " "series, designed to deliver exceptional performance for a variety of " "enterprise applications. With its cutting-edge features, it delivers greater " "accuracy and outstanding results, making it an excellent choice for " "organizations looking for a top-notch text processing solution." msgstr "" "Titan Text Premier 是 Titan Text 系列中功能强大且先进的型号,旨在为各种企业应" "用程序提供卓越的性能。凭借其尖端功能,它提供了更高的准确性和出色的结果,使其" "成为寻求一流文本处理解决方案的组织的绝佳选择。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85 msgid "" "Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-" "tuning English-language tasks, including summarization and copywriting, " "where customers require smaller, more cost-effective, and highly " "customizable models." msgstr "" "Amazon Titan Text Lite 是一种轻量级的高效模型,非常适合英语任务的微调,包括摘" "要和文案写作等,在这种场景下,客户需要更小、更经济高效且高度可定制的模型" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91 msgid "" "Amazon Titan Text Express has context lengths of up to 8,000 tokens, making " "it ideal for a variety of high-level general language tasks, such as open-" "ended text generation and conversational chat, as well as support in " "retrieval-augmented generation (RAG). At launch, the model is optimized for " "English, but other languages are supported." msgstr "" "Amazon Titan Text Express 的上下文长度长达 8000 个 tokens,因而非常适合各种高" "级常规语言任务,例如开放式文本生成和对话式聊天,以及检索增强生成(RAG)中的支" "持。在发布时,该模型针对英语进行了优化,但也支持其他语言。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97 msgid "" "7B dense converter for rapid deployment and easy customization. Small in " "size yet powerful in a variety of use cases. Supports English and code, as " "well as 32k context windows." msgstr "" "7B 密集型转换器,可快速部署,易于定制。体积虽小,但功能强大,适用于各种用例。" "支持英语和代码,以及 32k 的上下文窗口。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103 msgid "" "Advanced Mistral AI large-scale language model capable of handling any " "language task, including complex multilingual reasoning, text understanding, " "transformation, and code generation." msgstr "" "先进的 Mistral AI 大型语言模型,能够处理任何语言任务,包括复杂的多语言推理、" "文本理解、转换和代码生成。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109 msgid "" "Ideal for content creation, conversational AI, language understanding, R&D, " "and enterprise applications" msgstr "非常适合内容创作、对话式人工智能、语言理解、研发和企业智能体" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115 msgid "" "Ideal for limited computing power and resources, edge devices, and faster " "training times." msgstr "非常适合有限的计算能力和资源、边缘设备和更快的训练时间。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123 msgid "" "Titan Embed Text is the largest embedding model in the Amazon Titan Embed " "series and can handle various text embedding tasks, such as text " "classification, text similarity calculation, etc." msgstr "" "Titan Embed Text 是 Amazon Titan Embed 系列中最大的嵌入模型,可以处理各种文本" "嵌入任务,如文本分类、文本相似度计算等。" #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47 #, python-brace-format msgid "The following fields are required: {keys}" msgstr "以下字段是必填项: {keys}" #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:76 msgid "Verification failed, please check whether the parameters are correct" msgstr "认证失败,请检查参数是否正确" #: apps/models_provider/impl/azure_model_provider/credential/tti.py:28 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28 msgid "Picture quality" msgstr "图片质量" #: apps/models_provider/impl/azure_model_provider/credential/tts.py:17 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:17 msgid "" "Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) " "to find one that suits your desired tone and audience. The current voiceover " "is optimized for English." msgstr "" "尝试不同的声音(合金、回声、寓言、缟玛瑙、新星和闪光),找到一种适合您所需的" "音调和听众的声音。当前的语音针对英语进行了优化。" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24 msgid "Good at common conversational tasks, supports 32K contexts" msgstr "擅长通用对话任务,支持 32K 上下文" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29 msgid "Good at handling programming tasks, supports 16K contexts" msgstr "擅长处理编程任务,支持 16K 上下文" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32 msgid "Latest Gemini 1.0 Pro model, updated with Google update" msgstr "最新的 Gemini 1.0 Pro 模型,更新了 Google 更新" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36 msgid "Latest Gemini 1.0 Pro Vision model, updated with Google update" msgstr "最新的Gemini 1.0 Pro Vision模型,随Google更新而更新" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58 msgid "Latest Gemini 1.5 Flash model, updated with Google updates" msgstr "最新的Gemini 1.5 Flash模型,随Google更新而更新" #: apps/models_provider/impl/gemini_model_provider/model/stt.py:53 msgid "convert audio to text" msgstr "将音频转换为文本" #: apps/models_provider/impl/local_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:54 msgid "Model catalog" msgstr "模型目录" #: apps/models_provider/impl/local_model_provider/local_model_provider.py:39 msgid "local model" msgstr "本地模型" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44 msgid "API domain name is invalid" msgstr "API 域名无效" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48 msgid "The model does not exist, please download the model first" msgstr "模型不存在,请先下载模型" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 7B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一组经过预训练和微调的生成文本模型,其规模从 70 亿到 700 亿个不等。" "这是 7B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 13B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一组经过预训练和微调的生成文本模型,其规模从 70 亿到 700 亿个不等。" "这是 13B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 70B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一组经过预训练和微调的生成文本模型,其规模从 70 亿到 700 亿个不等。" "这是 70B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68 msgid "" "Since the Chinese alignment of Llama2 itself is weak, we use the Chinese " "instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so " "that it has strong Chinese conversation capabilities." msgstr "" "由于Llama2本身的中文对齐较弱,我们采用中文指令集,对meta-llama/Llama-2-13b-" "chat-hf进行LoRA微调,使其具备较强的中文对话能力。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72 msgid "" "Meta Llama 3: The most capable public product LLM to date. 8 billion " "parameters." msgstr "Meta Llama 3:迄今为止最有能力的公开产品LLM。80亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76 msgid "" "Meta Llama 3: The most capable public product LLM to date. 70 billion " "parameters." msgstr "Meta Llama 3:迄今为止最有能力的公开产品LLM。700亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80 msgid "" "Compared with previous versions, qwen 1.5 0.5b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 500 million parameters." msgstr "" "qwen 1.5 0.5b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有" "显著增强。所有规模的模型都支持32768个tokens的上下文长度。5亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84 msgid "" "Compared with previous versions, qwen 1.5 1.8b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 1.8 billion parameters." msgstr "" "qwen 1.5 1.8b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有" "显著增强。所有规模的模型都支持32768个tokens的上下文长度。18亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88 msgid "" "Compared with previous versions, qwen 1.5 4b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "4 billion parameters." msgstr "" "qwen 1.5 4b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有显" "著增强。所有规模的模型都支持32768个tokens的上下文长度。40亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93 msgid "" "Compared with previous versions, qwen 1.5 7b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "7 billion parameters." msgstr "" "qwen 1.5 7b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有显" "著增强。所有规模的模型都支持32768个tokens的上下文长度。70亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97 msgid "" "Compared with previous versions, qwen 1.5 14b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "14 billion parameters." msgstr "" "qwen 1.5 14b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有显" "著增强。所有规模的模型都支持32768个tokens的上下文长度。140亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101 msgid "" "Compared with previous versions, qwen 1.5 32b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "32 billion parameters." msgstr "" "qwen 1.5 32b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有显" "著增强。所有规模的模型都支持32768个tokens的上下文长度。320亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105 msgid "" "Compared with previous versions, qwen 1.5 72b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "72 billion parameters." msgstr "" "qwen 1.5 72b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有显" "著增强。所有规模的模型都支持32768个tokens的上下文长度。720亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109 msgid "" "Compared with previous versions, qwen 1.5 110b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 110 billion parameters." msgstr "" "qwen 1.5 110b 相较于以往版本,模型与人类偏好的对齐程度以及多语言处理能力上有" "显著增强。所有规模的模型都支持32768个tokens的上下文长度。1100亿参数。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193 msgid "" "Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open " "model." msgstr "Phi-3 Mini是Microsoft的3.8B参数,轻量级,最先进的开放模型。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197 msgid "" "A high-performance open embedding model with a large token context window." msgstr "一个具有大 tokens上下文窗口的高性能开放嵌入模型。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:16 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 " "or 1792x1024 pixels." msgstr "" "图像生成端点允许您根据文本提示创建原始图像。使用 DALL·E 3 时,图像的尺寸可以" "为 1024x1024、1024x1792 或 1792x1024 像素。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 msgid "" " \n" "By default, images are produced in standard quality, but with DALL·E 3 you " "can set quality: \"hd\" to enhance detail. Square, standard quality images " "are generated fastest.\n" " " msgstr "" "默认情况下,图像以标准质量生成,但使用 DALL·E 3 时,您可以设置质量:“hd”以增" "强细节。方形、标准质量的图像生成速度最快。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44 msgid "" "You can use DALL·E 3 to request 1 image at a time (requesting more images by " "issuing parallel requests), or use DALL·E 2 with the n parameter to request " "up to 10 images at a time." msgstr "" "您可以使用 DALL·E 3 一次请求 1 个图像(通过发出并行请求来请求更多图像),或者" "使用带有 n 参数的 DALL·E 2 一次最多请求 10 个图像。" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119 #: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118 msgid "The latest gpt-3.5-turbo, updated with OpenAI adjustments" msgstr "最新的gpt-3.5-turbo,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38 msgid "Latest gpt-4, updated with OpenAI adjustments" msgstr "最新的gpt-4,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99 msgid "" "The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI " "adjustments" msgstr "最新的GPT-4o,比gpt-4-turbo更便宜、更快,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102 msgid "" "The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI " "adjustments" msgstr "最新的gpt-4o-mini,比gpt-4o更便宜、更快,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46 msgid "The latest gpt-4-turbo, updated with OpenAI adjustments" msgstr "最新的gpt-4-turbo,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49 msgid "The latest gpt-4-turbo-preview, updated with OpenAI adjustments" msgstr "最新的gpt-4-turbo-preview,随OpenAI调整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53 msgid "" "gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 " "tokens" msgstr "2024年1月25日的gpt-3.5-turbo快照,支持上下文长度16,385 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57 msgid "" "gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 " "tokens" msgstr "2023年11月6日的gpt-3.5-turbo快照,支持上下文长度16,385 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61 msgid "" "[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June " "13, 2024" msgstr "[Legacy] 2023年6月13日的gpt-3.5-turbo快照,将于2024年6月13日弃用" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65 msgid "" "gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens" msgstr "2024年5月13日的gpt-4o快照,支持上下文长度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69 msgid "" "gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 " "tokens" msgstr "2024年4月9日的gpt-4-turbo快照,支持上下文长度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72 msgid "" "gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 " "tokens" msgstr "2024年1月25日的gpt-4-turbo快照,支持上下文长度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75 msgid "" "gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 " "tokens" msgstr "2023年11月6日的gpt-4-turbo快照,支持上下文长度128,000 tokens" #: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58 msgid "Tencent Cloud" msgstr "腾讯云" #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88 #, python-brace-format msgid "{keys} is required" msgstr "{keys} 是必填项" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "painting style" msgstr "绘画风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "If not passed, the default value is 201 (Japanese anime style)" msgstr "如果未传递,则默认值为201(日本动漫风格)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18 msgid "Not limited to style" msgstr "不限于风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19 msgid "ink painting" msgstr "水墨画" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20 msgid "concept art" msgstr "概念艺术" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21 msgid "Oil painting 1" msgstr "油画1" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22 msgid "Oil Painting 2 (Van Gogh)" msgstr "油画2(梵高)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23 msgid "watercolor painting" msgstr "水彩画" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24 msgid "pixel art" msgstr "像素画" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25 msgid "impasto style" msgstr "厚涂风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26 msgid "illustration" msgstr "插图" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27 msgid "paper cut style" msgstr "剪纸风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28 msgid "Impressionism 1 (Monet)" msgstr "印象派1(莫奈)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29 msgid "Impressionism 2" msgstr "印象派2" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31 msgid "classical portraiture" msgstr "古典肖像画" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32 msgid "black and white sketch" msgstr "黑白素描画" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33 msgid "cyberpunk" msgstr "赛博朋克" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34 msgid "science fiction style" msgstr "科幻风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35 msgid "dark style" msgstr "暗黑风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37 msgid "vaporwave" msgstr "蒸汽波" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38 msgid "Japanese animation" msgstr "日系动漫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39 msgid "monster style" msgstr "怪兽风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40 msgid "Beautiful ancient style" msgstr "唯美古风" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41 msgid "retro anime" msgstr "复古动漫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42 msgid "Game cartoon hand drawing" msgstr "游戏卡通手绘" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43 msgid "Universal realistic style" msgstr "通用写实风格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "Generate image resolution" msgstr "生成图像分辨率" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "If not transmitted, the default value is 768:768." msgstr "不传默认使用768:768。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38 msgid "" "The most effective version of the current hybrid model, the trillion-level " "parameter scale MOE-32K long article model. Reaching the absolute leading " "level on various benchmarks, with complex instructions and reasoning, " "complex mathematical capabilities, support for function call, and " "application focus optimization in fields such as multi-language translation, " "finance, law, and medical care" msgstr "" "当前混元模型中效果最优版本,万亿级参数规模 MOE-32K 长文模型。在各种 " "benchmark 上达到绝对领先的水平,复杂指令和推理,具备复杂数学能力,支持 " "functioncall,在多语言翻译、金融法律医疗等领域智能体重点优化" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45 msgid "" "A better routing strategy is adopted to simultaneously alleviate the " "problems of load balancing and expert convergence. For long articles, the " "needle-in-a-haystack index reaches 99.9%" msgstr "" "采用更优的路由策略,同时缓解了负载均衡和专家趋同的问题。长文方面,大海捞针指" "标达到99.9%" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51 msgid "" "Upgraded to MOE structure, the context window is 256k, leading many open " "source models in multiple evaluation sets such as NLP, code, mathematics, " "industry, etc." msgstr "" "升级为 MOE 结构,上下文窗口为 256k ,在 NLP,代码,数学,行业等多项评测集上领" "先众多开源模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57 msgid "" "Hunyuan's latest version of the role-playing model, a role-playing model " "launched by Hunyuan's official fine-tuning training, is based on the Hunyuan " "model combined with the role-playing scene data set for additional training, " "and has better basic effects in role-playing scenes." msgstr "" "混元最新版角色扮演模型,混元官方精调训练推出的角色扮演模型,基于混元模型结合" "角色扮演场景数据集进行增训,在角色扮演场景具有更好的基础效果" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63 msgid "" "Hunyuan's latest MOE architecture FunctionCall model has been trained with " "high-quality FunctionCall data and has a context window of 32K, leading in " "multiple dimensions of evaluation indicators." msgstr "" "混元最新 MOE 架构 FunctionCall 模型,经过高质量的 FunctionCall 数据训练,上下" "文窗口达 32K,在多个维度的评测指标上处于领先。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69 msgid "" "Hunyuan's latest code generation model, after training the base model with " "200B high-quality code data, and iterating on high-quality SFT data for half " "a year, the context long window length has been increased to 8K, and it " "ranks among the top in the automatic evaluation indicators of code " "generation in the five major languages; the five major languages In the " "manual high-quality evaluation of 10 comprehensive code tasks that consider " "all aspects, the performance is in the first echelon." msgstr "" "混元最新代码生成模型,经过 200B 高质量代码数据增训基座模型,迭代半年高质量 " "SFT 数据训练,上下文长窗口长度增大到 8K,五大语言代码生成自动评测指标上位居前" "列;五大语言10项考量各方面综合代码任务人工高质量评测上,性能处于第一梯队" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77 msgid "" "Tencent's Hunyuan Embedding interface can convert text into high-quality " "vector data. The vector dimension is 1024 dimensions." msgstr "" "腾讯混元 Embedding 接口,可以将文本转化为高质量的向量数据。向量维度为1024维。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87 msgid "Mixed element visual model" msgstr "混元视觉模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94 msgid "Hunyuan graph model" msgstr "混元生图模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125 msgid "Tencent Hunyuan" msgstr "腾讯混元" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24 #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42 msgid "Facebook’s 125M parameter model" msgstr "Facebook的125M参数模型" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25 msgid "BAAI’s 7B parameter model" msgstr "BAAI的7B参数模型" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26 msgid "BAAI’s 13B parameter mode" msgstr "BAAI的13B参数模型" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16 msgid "" "If the gap between width, height and 512 is too large, the picture rendering " "effect will be poor and the probability of excessive delay will increase " "significantly. Recommended ratio and corresponding width and height before " "super score: width*height" msgstr "" "宽、高与512差距过大,则出图效果不佳、延迟过长概率显著增加。超分前建议比例及对" "应宽高:width*height" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15 msgid "timbre" msgstr "音色" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "speaking speed" msgstr "语速" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 msgid "[0.2,3], the default is 1, usually one decimal place is enough" msgstr "[0.2,3],默认为1,通常保留一位小数即可" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88 msgid "" "The user goes to the model inference page of Volcano Ark to create an " "inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call " "it." msgstr "" "用户前往火山方舟的模型推理页面创建推理接入点,这里需要输入ep-xxxxxxxxxx-yyyy" "进行调用" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59 msgid "Universal 2.0-Vincent Diagram" msgstr "通用2.0-文生图" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64 msgid "Universal 2.0Pro-Vincent Chart" msgstr "通用2.0Pro-文生图" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69 msgid "Universal 1.4-Vincent Chart" msgstr "通用1.4-文生图" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74 msgid "Animation 1.3.0-Vincent Picture" msgstr "动漫1.3.0-文生图" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79 msgid "Animation 1.3.1-Vincent Picture" msgstr "动漫1.3.1-文生图" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113 msgid "volcano engine" msgstr "火山引擎" #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51 #, python-brace-format msgid "{model_name} The model does not support" msgstr "{model_name} 模型不支持" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24 #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53 msgid "" "ERNIE-Bot-4 is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" "ERNIE-Bot-4是百度自行研发的大语言模型,覆盖海量中文数据,具有更强的对话问答、" "内容创作生成等能力。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27 msgid "" "ERNIE-Bot is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" "ERNIE-Bot是百度自行研发的大语言模型,覆盖海量中文数据,具有更强的对话问答、内" "容创作生成等能力。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30 msgid "" "ERNIE-Bot-turbo is a large language model independently developed by Baidu. " "It covers massive Chinese data, has stronger capabilities in dialogue Q&A, " "content creation and generation, and has a faster response speed." msgstr "" "ERNIE-Bot-turbo是百度自行研发的大语言模型,覆盖海量中文数据,具有更强的对话问" "答、内容创作生成等能力,响应速度更快。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33 msgid "" "BLOOMZ-7B is a well-known large language model in the industry. It was " "developed and open sourced by BigScience and can output text in 46 languages " "and 13 programming languages." msgstr "" "BLOOMZ-7B是业内知名的大语言模型,由BigScience研发并开源,能够以46种语言和13种" "编程语言输出文本。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39 msgid "" "Llama-2-13b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning and knowledge application. " "Llama-2-13b-chat is a native open source version with balanced performance " "and effect, suitable for conversation scenarios." msgstr "" "Llama-2-13b-chat由Meta AI研发并开源,在编码、推理及知识智能体等场景表现优秀," "Llama-2-13b-chat是性能与效果均衡的原生开源版本,适用于对话场景。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42 msgid "" "Llama-2-70b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning, and knowledge application. " "Llama-2-70b-chat is a native open source version with high-precision effects." msgstr "" "Llama-2-70b-chat由Meta AI研发并开源,在编码、推理及知识智能体等场景表现优秀," "Llama-2-70b-chat是高精度效果的原生开源版本。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45 msgid "" "The Chinese enhanced version developed by the Qianfan team based on " "Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-" "EVAL." msgstr "" "千帆团队在Llama-2-7b基础上的中文增强版本,在CMMLU、C-EVAL等中文知识库上表现优" "异。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49 msgid "" "Embedding-V1 is a text representation model based on Baidu Wenxin large " "model technology. It can convert text into a vector form represented by " "numerical values and can be used in text retrieval, information " "recommendation, knowledge mining and other scenarios. Embedding-V1 provides " "the Embeddings interface, which can generate corresponding vector " "representations based on input content. You can call this interface to input " "text into the model and obtain the corresponding vector representation for " "subsequent text processing and analysis." msgstr "" "Embedding-V1是一个基于百度文心大模型技术的文本表示模型,可以将文本转化为用数" "值表示的向量形式,用于文本检索、信息推荐、知识挖掘等场景。 Embedding-V1提供了" "Embeddings接口,可以根据输入内容生成对应的向量表示。您可以通过调用该接口,将" "文本输入到模型中,获取到对应的向量表示,从而进行后续的文本处理和分析。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66 msgid "Thousand sails large model" msgstr "千帆大模型" #: apps/models_provider/impl/xf_model_provider/credential/image.py:42 msgid "Please outline this picture" msgstr "请描述这张图片" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:15 msgid "Speaker" msgstr "发音人" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:16 msgid "" "Speaker, optional value: Please go to the console to add a trial or purchase " "speaker. After adding, the speaker parameter value will be displayed." msgstr "" "发音人,可选值:请到控制台添加试用或购买发音人,添加后即显示发音人参数值" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:21 msgid "iFlytek Xiaoyan" msgstr "讯飞小燕" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:22 msgid "iFlytek Xujiu" msgstr "讯飞许久" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:23 msgid "iFlytek Xiaoping" msgstr "讯飞小萍" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:24 msgid "iFlytek Xiaojing" msgstr "讯飞小婧" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:25 msgid "iFlytek Xuxiaobao" msgstr "讯飞许小宝" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "Speech speed, optional value: [0-100], default is 50" msgstr "语速,可选值:[0-100],默认为50" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39 #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50 msgid "Chinese and English recognition" msgstr "中英文识别" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66 msgid "iFlytek Spark" msgstr "讯飞星火" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. The dimensions of the image can be 1024x1024, 1024x1792, or " "1792x1024 pixels." msgstr "" "图像生成端点允许您根据文本提示创建原始图像。图像的尺寸可以为 1024x1024、" "1024x1792 或 1792x1024 像素。" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29 msgid "" "By default, images are generated in standard quality, you can set quality: " "\"hd\" to enhance detail. Square, standard quality images are generated " "fastest." msgstr "" "默认情况下,图像以标准质量生成,您可以设置质量:“hd”以增强细节。方形、标准质" "量的图像生成速度最快。" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42 msgid "" "You can request 1 image at a time (requesting more images by making parallel " "requests), or up to 10 images at a time using the n parameter." msgstr "" "您可以一次请求 1 个图像(通过发出并行请求来请求更多图像),或者使用 n 参数一" "次最多请求 10 个图像。" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20 msgid "Chinese female" msgstr "中文女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21 msgid "Chinese male" msgstr "中文男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22 msgid "Japanese male" msgstr "日语男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23 msgid "Cantonese female" msgstr "粤语女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24 msgid "English female" msgstr "英文女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25 msgid "English male" msgstr "英文男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26 msgid "Korean female" msgstr "韩语女" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37 msgid "" "Code Llama is a language model specifically designed for code generation." msgstr "Code Llama 是一个专门用于代码生成的语言模型。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44 msgid "" " \n" "Code Llama Instruct is a fine-tuned version of Code Llama's instructions, " "designed to perform specific tasks.\n" " " msgstr "" "Code Llama Instruct 是 Code Llama 的指令微调版本,专为执行特定任务而设计。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53 msgid "" "Code Llama Python is a language model specifically designed for Python code " "generation." msgstr "Code Llama Python 是一个专门用于 Python 代码生成的语言模型。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60 msgid "" "CodeQwen 1.5 is a language model for code generation with high performance." msgstr "CodeQwen 1.5 是一个用于代码生成的语言模型,具有较高的性能。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67 msgid "CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5." msgstr "CodeQwen 1.5 Chat 是一个聊天模型版本的 CodeQwen 1.5。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74 msgid "Deepseek is a large-scale language model with 13 billion parameters." msgstr "Deepseek Chat 是一个聊天模型版本的 Deepseek。" #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16 msgid "" "Image size, only cogview-3-plus supports this parameter. Optional range: " "[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the " "default is 1024x1024." msgstr "" "图片尺寸,仅 cogview-3-plus 支持该参数。可选范围:" "[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440],默认是" "1024x1024。" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34 msgid "" "Have strong multi-modal understanding capabilities. Able to understand up to " "five images simultaneously and supports video content understanding" msgstr "具有强大的多模态理解能力。能够同时理解多达五张图像,并支持视频内容理解" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis" msgstr "专注于单图理解。适用于需要高效图像解析的场景" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis (free)" msgstr "专注于单图理解。适用于需要高效图像解析的场景(免费)" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46 msgid "" "Quickly and accurately generate images based on user text descriptions. " "Resolution supports 1024x1024" msgstr "根据用户文字描述快速、精准生成图像。分辨率支持1024x1024" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes" msgstr "根据用户文字描述生成高质量图像,支持多图片尺寸" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes (free)" msgstr "根据用户文字描述生成高质量图像,支持多图片尺寸(免费)" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75 msgid "zhipu AI" msgstr "智谱 AI" #: apps/models_provider/serializers/model_apply_serializers.py:32 #: apps/models_provider/serializers/model_apply_serializers.py:37 msgid "vector text" msgstr "向量文本" #: apps/models_provider/serializers/model_apply_serializers.py:33 msgid "vector text list" msgstr "向量文本列表" #: apps/models_provider/serializers/model_apply_serializers.py:41 msgid "text" msgstr "文本" #: apps/models_provider/serializers/model_apply_serializers.py:42 msgid "metadata" msgstr "元数据" #: apps/models_provider/serializers/model_apply_serializers.py:47 msgid "query" msgstr "查询" #: apps/models_provider/serializers/model_serializer.py:44 #: apps/models_provider/serializers/model_serializer.py:257 msgid "parameter configuration" msgstr "参数配置" #: apps/models_provider/serializers/model_serializer.py:45 #: apps/models_provider/serializers/model_serializer.py:222 #: apps/models_provider/serializers/model_serializer.py:258 msgid "certification information" msgstr "认证信息" #: apps/models_provider/serializers/model_serializer.py:118 msgid "Shared models cannot be deleted or modified" msgstr "共享模型不能被删除或修改" #: apps/models_provider/serializers/model_serializer.py:230 #: apps/models_provider/serializers/model_serializer.py:269 #, python-brace-format msgid "base model【{model_name}】already exists" msgstr "模型【{model_name}】已存在" #: apps/models_provider/serializers/model_serializer.py:309 msgid "Model saving failed" msgstr "模型保存失败" #: apps/models_provider/views/model.py:60 #: apps/models_provider/views/model.py:61 #: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55 #: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57 msgid "Create model" msgstr "创建模型" #: apps/models_provider/views/model.py:90 #: apps/models_provider/views/model.py:91 #: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84 #: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86 msgid "Query model list" msgstr "查询模型列表" #: apps/models_provider/views/model.py:107 #: apps/models_provider/views/model.py:108 #: apps/models_provider/views/model.py:109 #: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102 #: apps/shared/views/shared_model.py:103 msgid "Update model" msgstr "更新模型" #: apps/models_provider/views/model.py:125 #: apps/models_provider/views/model.py:126 #: apps/models_provider/views/model.py:127 #: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120 #: apps/shared/views/shared_model.py:121 msgid "Delete model" msgstr "删除模型" #: apps/models_provider/views/model.py:140 #: apps/models_provider/views/model.py:141 #: apps/models_provider/views/model.py:142 #: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134 #: apps/shared/views/shared_model.py:135 msgid "Query model details" msgstr "查询模型详情" #: apps/models_provider/views/model.py:155 #: apps/models_provider/views/model.py:156 #: apps/models_provider/views/model.py:157 #: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149 #: apps/shared/views/shared_model.py:150 msgid "Get model parameter form" msgstr "获取模型参数表单" #: apps/models_provider/views/model.py:167 #: apps/models_provider/views/model.py:168 #: apps/models_provider/views/model.py:169 #: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161 #: apps/shared/views/shared_model.py:162 msgid "Save model parameter form" msgstr "保存模型参数表单" #: apps/models_provider/views/model.py:187 #: apps/models_provider/views/model.py:189 #: apps/models_provider/views/model.py:191 #: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181 #: apps/shared/views/shared_model.py:183 msgid "" "Query model meta information, this interface does not carry authentication " "information" msgstr "查询模型元信息,该接口不携带认证信息" #: apps/models_provider/views/model.py:204 #: apps/models_provider/views/model.py:205 #: apps/models_provider/views/model.py:206 #: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197 #: apps/shared/views/shared_model.py:198 msgid "Pause model download" msgstr "下载模型暂停" #: apps/models_provider/views/model.py:222 #: apps/models_provider/views/model.py:223 #: apps/models_provider/views/model.py:224 msgid "Get Share model" msgstr "获取共享模型" #: apps/models_provider/views/model_apply.py:25 #: apps/models_provider/views/model_apply.py:26 #: apps/models_provider/views/model_apply.py:27 #: apps/models_provider/views/model_apply.py:37 #: apps/models_provider/views/model_apply.py:38 #: apps/models_provider/views/model_apply.py:39 msgid "Vectorization documentation" msgstr "向量化文档" #: apps/models_provider/views/model_apply.py:49 #: apps/models_provider/views/model_apply.py:50 #: apps/models_provider/views/model_apply.py:51 msgid "Reorder documents" msgstr "重新排序文档" #: apps/models_provider/views/provide.py:21 #: apps/models_provider/views/provide.py:22 #: apps/models_provider/views/provide.py:23 msgid "Get a list of model suppliers" msgstr "获取模型供应商列表" #: apps/models_provider/views/provide.py:43 #: apps/models_provider/views/provide.py:44 #: apps/models_provider/views/provide.py:45 msgid "Get a list of model types" msgstr "获取模型类型列表" #: apps/models_provider/views/provide.py:57 #: apps/models_provider/views/provide.py:58 #: apps/models_provider/views/provide.py:59 msgid "Example of obtaining model list" msgstr "获取模型列表示例" #: apps/models_provider/views/provide.py:75 #: apps/models_provider/views/provide.py:76 #: apps/models_provider/views/provide.py:77 msgid "Get model default parameters" msgstr "获取模型默认参数" #: apps/models_provider/views/provide.py:92 #: apps/models_provider/views/provide.py:93 #: apps/models_provider/views/provide.py:94 msgid "Get the model creation form" msgstr "获取模型创建表单" #: apps/oss/serializers/file.py:80 msgid "File not found" msgstr "文件未找到" #: apps/oss/views/file.py:21 apps/oss/views/file.py:22 #: apps/oss/views/file.py:23 msgid "Upload file" msgstr "上传文件" #: apps/oss/views/file.py:27 apps/oss/views/file.py:41 #: apps/oss/views/file.py:53 msgid "File" msgstr "文件" #: apps/oss/views/file.py:36 apps/oss/views/file.py:37 #: apps/oss/views/file.py:38 msgid "Get file" msgstr "获取文件" #: apps/oss/views/file.py:48 apps/oss/views/file.py:49 #: apps/oss/views/file.py:50 msgid "Delete file" msgstr "删除文件" #: apps/resource_manage/views/document.py:30 #: apps/resource_manage/views/document.py:31 #: apps/resource_manage/views/document.py:32 msgid "Create system knowledge" msgstr "创建系统知识库" #: apps/resource_manage/views/document.py:36 #: apps/resource_manage/views/document.py:56 #: apps/resource_manage/views/document.py:83 #: apps/resource_manage/views/document.py:113 #: apps/resource_manage/views/document.py:130 #: apps/resource_manage/views/document.py:155 #: apps/resource_manage/views/document.py:183 #: apps/resource_manage/views/document.py:210 #: apps/resource_manage/views/document.py:237 #: apps/resource_manage/views/document.py:267 #: apps/resource_manage/views/document.py:296 #: apps/resource_manage/views/document.py:317 #: apps/resource_manage/views/document.py:345 #: apps/resource_manage/views/document.py:362 #: apps/resource_manage/views/document.py:383 #: apps/resource_manage/views/document.py:411 #: apps/resource_manage/views/document.py:435 #: apps/resource_manage/views/document.py:460 #: apps/resource_manage/views/document.py:485 #: apps/resource_manage/views/document.py:507 #: apps/resource_manage/views/document.py:530 #: apps/resource_manage/views/document.py:553 #: apps/resource_manage/views/document.py:571 #: apps/resource_manage/views/document.py:585 msgid "System Knowledge/Documentation" msgstr "系统知识库/文档" #: apps/resource_manage/views/document.py:51 #: apps/resource_manage/views/document.py:52 #: apps/resource_manage/views/document.py:53 msgid "Get system document" msgstr "获取文档" #: apps/resource_manage/views/document.py:77 #: apps/resource_manage/views/document.py:78 #: apps/resource_manage/views/document.py:79 msgid "Segmented system document" msgstr "分段文档" #: apps/resource_manage/views/document.py:108 #: apps/resource_manage/views/document.py:109 #: apps/resource_manage/views/document.py:110 msgid "Get a list of system segment IDs" msgstr "获取分段ID列表" #: apps/resource_manage/views/document.py:124 #: apps/resource_manage/views/document.py:125 #: apps/resource_manage/views/document.py:126 msgid "Cancel system tasks in batches" msgstr "批量取消任务" #: apps/resource_manage/views/document.py:149 #: apps/resource_manage/views/document.py:150 #: apps/resource_manage/views/document.py:151 msgid "Create system knowledges in batches" msgstr "批量创建知识库" #: apps/resource_manage/views/document.py:177 #: apps/resource_manage/views/document.py:178 #: apps/resource_manage/views/document.py:179 msgid "Batch sync system knowledges" msgstr "批量同步知识库" #: apps/resource_manage/views/document.py:204 #: apps/resource_manage/views/document.py:206 msgid "Delete system document in batches" msgstr "批量删除文档" #: apps/resource_manage/views/document.py:205 msgid "Delete system knowledge in batches" msgstr "批量删除知识库" #: apps/resource_manage/views/document.py:232 #: apps/resource_manage/views/document.py:233 msgid "Batch refresh system document vector library" msgstr "批量刷新文档向量库" #: apps/resource_manage/views/document.py:261 #: apps/resource_manage/views/document.py:262 #: apps/resource_manage/views/document.py:263 msgid "Batch generate related system problems" msgstr "批量生成相关问题" #: apps/resource_manage/views/document.py:290 #: apps/resource_manage/views/document.py:291 #: apps/resource_manage/views/document.py:292 msgid "Modify system document hit processing methods in batches" msgstr "批量修改文档命中处理方式" #: apps/resource_manage/views/document.py:312 #: apps/resource_manage/views/document.py:313 msgid "Migrate system knowledges in batches" msgstr "批量迁移知识库" #: apps/resource_manage/views/document.py:340 #: apps/resource_manage/views/document.py:341 #: apps/resource_manage/views/document.py:342 msgid "Get system document details" msgstr "获取文档详情" #: apps/resource_manage/views/document.py:356 #: apps/resource_manage/views/document.py:357 #: apps/resource_manage/views/document.py:358 msgid "Modify system document" msgstr "修改文档" #: apps/resource_manage/views/document.py:378 #: apps/resource_manage/views/document.py:379 #: apps/resource_manage/views/document.py:380 msgid "Delete system document" msgstr "删除文档" #: apps/resource_manage/views/document.py:405 #: apps/resource_manage/views/document.py:406 #: apps/resource_manage/views/document.py:407 msgid "Synchronize system web site types" msgstr "同步网站类型" #: apps/resource_manage/views/document.py:429 #: apps/resource_manage/views/document.py:430 #: apps/resource_manage/views/document.py:431 msgid "Refresh system knowledge vector library" msgstr "刷新文档向量库" #: apps/resource_manage/views/document.py:454 #: apps/resource_manage/views/document.py:455 #: apps/resource_manage/views/document.py:456 msgid "Cancel system task" msgstr "取消任务" #: apps/resource_manage/views/document.py:480 #: apps/resource_manage/views/document.py:481 #: apps/resource_manage/views/document.py:482 msgid "Get system document by pagination" msgstr "分页获取文档" #: apps/resource_manage/views/document.py:503 #: apps/resource_manage/views/document.py:504 msgid "Export system knowledge" msgstr "导出知识库" #: apps/resource_manage/views/document.py:526 #: apps/resource_manage/views/document.py:527 msgid "Export Zip system knowledge" msgstr "导出Zip知识库" #: apps/resource_manage/views/document.py:549 #: apps/resource_manage/views/document.py:550 msgid "Download system source file" msgstr "下载系统源文件" #: apps/resource_manage/views/document.py:567 #: apps/resource_manage/views/document.py:568 msgid "Get system QA template" msgstr "获取系统问答模板" #: apps/resource_manage/views/document.py:581 #: apps/resource_manage/views/document.py:582 msgid "Get system form template" msgstr "获取系统表单模板" #: apps/resource_manage/views/knowledge.py:26 #: apps/resource_manage/views/knowledge.py:27 #: apps/resource_manage/views/knowledge.py:28 msgid "Get system knowledge list" msgstr "获取系统知识库列表" #: apps/resource_manage/views/knowledge.py:31 #: apps/resource_manage/views/knowledge.py:50 #: apps/resource_manage/views/knowledge.py:72 #: apps/resource_manage/views/knowledge.py:87 #: apps/resource_manage/views/knowledge.py:102 #: apps/resource_manage/views/knowledge.py:121 #: apps/resource_manage/views/knowledge.py:147 #: apps/resource_manage/views/knowledge.py:174 #: apps/resource_manage/views/knowledge.py:192 #: apps/resource_manage/views/knowledge.py:210 #: apps/resource_manage/views/knowledge.py:231 #: apps/resource_manage/views/knowledge.py:252 #: apps/resource_manage/views/knowledge.py:272 msgid "System Knowledge" msgstr "系统知识库" #: apps/resource_manage/views/knowledge.py:45 #: apps/resource_manage/views/knowledge.py:46 #: apps/resource_manage/views/knowledge.py:47 msgid "Get system knowledge list by pagination" msgstr "获取系统知识库分页列表" #: apps/resource_manage/views/knowledge.py:66 #: apps/resource_manage/views/knowledge.py:67 #: apps/resource_manage/views/knowledge.py:68 msgid "Update system knowledge" msgstr "更新系统知识库" #: apps/resource_manage/views/knowledge.py:82 #: apps/resource_manage/views/knowledge.py:83 #: apps/resource_manage/views/knowledge.py:84 msgid "Get system knowledge" msgstr "获取知识库" #: apps/resource_manage/views/knowledge.py:97 #: apps/resource_manage/views/knowledge.py:98 #: apps/resource_manage/views/knowledge.py:99 msgid "Delete system knowledge" msgstr "删除知识库" #: apps/resource_manage/views/knowledge.py:115 #: apps/resource_manage/views/knowledge.py:116 #: apps/resource_manage/views/knowledge.py:117 msgid "Synchronize the system knowledge base of the website" msgstr "同步网站知识库" #: apps/resource_manage/views/knowledge.py:141 #: apps/resource_manage/views/knowledge.py:142 #: apps/resource_manage/views/knowledge.py:143 msgid "System Hit test list" msgstr "命中测试列表" #: apps/resource_manage/views/knowledge.py:168 #: apps/resource_manage/views/knowledge.py:169 #: apps/resource_manage/views/knowledge.py:170 msgid "System Re-vectorize" msgstr "重新向量化" #: apps/resource_manage/views/knowledge.py:188 #: apps/resource_manage/views/knowledge.py:189 msgid "Export system knowledge base" msgstr "导出系统知识库" #: apps/resource_manage/views/knowledge.py:206 #: apps/resource_manage/views/knowledge.py:207 msgid "Export system knowledge base containing images" msgstr "导出包含图片的系统知识库" #: apps/resource_manage/views/knowledge.py:225 #: apps/resource_manage/views/knowledge.py:226 #: apps/resource_manage/views/knowledge.py:227 msgid "System generate related" msgstr "生成相关" #: apps/resource_manage/views/knowledge.py:247 #: apps/resource_manage/views/knowledge.py:248 #: apps/resource_manage/views/knowledge.py:249 msgid "Get model for system knowledge base" msgstr "获取系统知识库模型" #: apps/resource_manage/views/knowledge.py:267 #: apps/resource_manage/views/knowledge.py:268 #: apps/resource_manage/views/knowledge.py:269 msgid "Get embedding model for system knowledge base" msgstr "获取系统知识库嵌入模型" #: apps/resource_manage/views/paragraph.py:24 #: apps/resource_manage/views/paragraph.py:25 #: apps/resource_manage/views/paragraph.py:26 msgid "System paragraph list" msgstr "段落列表" #: apps/resource_manage/views/paragraph.py:29 #: apps/resource_manage/views/paragraph.py:50 #: apps/resource_manage/views/paragraph.py:76 #: apps/resource_manage/views/paragraph.py:93 #: apps/resource_manage/views/paragraph.py:125 #: apps/resource_manage/views/paragraph.py:151 #: apps/resource_manage/views/paragraph.py:179 #: apps/resource_manage/views/paragraph.py:201 #: apps/resource_manage/views/paragraph.py:233 #: apps/resource_manage/views/paragraph.py:259 #: apps/resource_manage/views/paragraph.py:283 #: apps/resource_manage/views/paragraph.py:314 #: apps/resource_manage/views/paragraph.py:344 #: apps/resource_manage/views/paragraph.py:370 msgid "System Knowledge/Documentation/Paragraph" msgstr "系统知识库/文档/段落" #: apps/resource_manage/views/paragraph.py:45 #: apps/resource_manage/views/paragraph.py:46 msgid "Create system paragraph" msgstr "创建段落" #: apps/resource_manage/views/paragraph.py:70 #: apps/resource_manage/views/paragraph.py:71 #: apps/resource_manage/views/paragraph.py:72 msgid "Batch system paragraph" msgstr "批量关联段落" #: apps/resource_manage/views/paragraph.py:88 #: apps/resource_manage/views/paragraph.py:89 msgid "Migrate system paragraphs in batches" msgstr "批量迁移段落" #: apps/resource_manage/views/paragraph.py:119 #: apps/resource_manage/views/paragraph.py:120 #: apps/resource_manage/views/paragraph.py:121 msgid "Batch generate system related" msgstr "批量生成相关" #: apps/resource_manage/views/paragraph.py:145 #: apps/resource_manage/views/paragraph.py:146 #: apps/resource_manage/views/paragraph.py:147 msgid "Modify system paragraph data" msgstr "修改段落数据" #: apps/resource_manage/views/paragraph.py:174 #: apps/resource_manage/views/paragraph.py:175 #: apps/resource_manage/views/paragraph.py:176 msgid "Get system paragraph details" msgstr "获取段落详情" #: apps/resource_manage/views/paragraph.py:196 #: apps/resource_manage/views/paragraph.py:197 #: apps/resource_manage/views/paragraph.py:198 msgid "Delete system paragraph" msgstr "删除段落" #: apps/resource_manage/views/paragraph.py:227 #: apps/resource_manage/views/paragraph.py:228 #: apps/resource_manage/views/paragraph.py:229 msgid "Add system associated questions" msgstr "添加关联问题" #: apps/resource_manage/views/paragraph.py:254 #: apps/resource_manage/views/paragraph.py:255 #: apps/resource_manage/views/paragraph.py:256 msgid "Get a list of system paragraph questions" msgstr "获取段落问题列表" #: apps/resource_manage/views/paragraph.py:277 #: apps/resource_manage/views/paragraph.py:278 #: apps/resource_manage/views/paragraph.py:279 msgid "Disassociation system issue" msgstr "取消关联问题" #: apps/resource_manage/views/paragraph.py:308 #: apps/resource_manage/views/paragraph.py:309 #: apps/resource_manage/views/paragraph.py:310 msgid "Related system questions" msgstr "关联问题" #: apps/resource_manage/views/paragraph.py:339 #: apps/resource_manage/views/paragraph.py:340 #: apps/resource_manage/views/paragraph.py:341 msgid "Get system paragraph list by pagination" msgstr "获取段落列表" #: apps/resource_manage/views/problem.py:23 #: apps/resource_manage/views/problem.py:24 #: apps/resource_manage/views/problem.py:25 msgid "System question list" msgstr "问题列表" #: apps/resource_manage/views/problem.py:28 #: apps/resource_manage/views/problem.py:50 #: apps/resource_manage/views/problem.py:71 #: apps/resource_manage/views/problem.py:94 #: apps/resource_manage/views/problem.py:115 #: apps/resource_manage/views/problem.py:135 #: apps/resource_manage/views/problem.py:158 #: apps/resource_manage/views/problem.py:182 msgid "System Knowledge/Documentation/Paragraph/Question" msgstr "系统知识库/文档/段落/问题" #: apps/resource_manage/views/problem.py:44 #: apps/resource_manage/views/problem.py:45 #: apps/resource_manage/views/problem.py:46 msgid "Create system question" msgstr "创建问题" #: apps/resource_manage/views/problem.py:66 #: apps/resource_manage/views/problem.py:67 #: apps/resource_manage/views/problem.py:68 msgid "Get a list of associated system paragraphs" msgstr "获取关联段落列表" #: apps/resource_manage/views/problem.py:88 #: apps/resource_manage/views/problem.py:89 #: apps/resource_manage/views/problem.py:90 msgid "Batch associated system paragraphs" msgstr "批量关联段落" #: apps/resource_manage/views/problem.py:109 #: apps/resource_manage/views/problem.py:110 #: apps/resource_manage/views/problem.py:111 msgid "Batch deletion system issues" msgstr "批量删除问题" #: apps/resource_manage/views/problem.py:130 #: apps/resource_manage/views/problem.py:131 #: apps/resource_manage/views/problem.py:132 msgid "Delete system question" msgstr "删除问题" #: apps/resource_manage/views/problem.py:152 #: apps/resource_manage/views/problem.py:153 #: apps/resource_manage/views/problem.py:154 msgid "Modify system question" msgstr "修改问题" #: apps/resource_manage/views/problem.py:177 #: apps/resource_manage/views/problem.py:178 #: apps/resource_manage/views/problem.py:179 msgid "Get the list of system questions by page" msgstr "分页获取问题列表" #: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25 #: apps/resource_manage/views/tool.py:26 msgid "Get system tool" msgstr "获取工具" #: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50 #: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80 #: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119 #: apps/resource_manage/views/tool.py:137 #: apps/resource_manage/views/tool.py:156 #: apps/resource_manage/views/tool.py:179 msgid "System Tool" msgstr "工具" #: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45 #: apps/resource_manage/views/tool.py:46 msgid "Update system tool" msgstr "更新工具" #: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61 #: apps/resource_manage/views/tool.py:62 msgid "Get system tool by id" msgstr "获取工具" #: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76 #: apps/resource_manage/views/tool.py:77 msgid "Delete system tool" msgstr "删除工具" #: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94 #: apps/resource_manage/views/tool.py:95 msgid "Get system tool list by pagination" msgstr "获取工具列表" #: apps/resource_manage/views/tool.py:114 #: apps/resource_manage/views/tool.py:115 #: apps/resource_manage/views/tool.py:116 msgid "Export system tool" msgstr "导出工具" #: apps/resource_manage/views/tool.py:132 #: apps/resource_manage/views/tool.py:133 #: apps/resource_manage/views/tool.py:134 msgid "Debug system tool" msgstr "调试工具" #: apps/resource_manage/views/tool.py:150 #: apps/resource_manage/views/tool.py:151 #: apps/resource_manage/views/tool.py:152 msgid "Check system code" msgstr "检查代码" #: apps/resource_manage/views/tool.py:173 #: apps/resource_manage/views/tool.py:174 #: apps/resource_manage/views/tool.py:175 msgid "Edit system tool icon" msgstr "修改工具图标" #: apps/role_setting/api/role_setting.py:16 #: apps/role_setting/api/role_setting.py:22 #: apps/role_setting/api/role_setting.py:33 #: apps/role_setting/api/role_setting.py:143 #: apps/role_setting/serializers/role_setting_serializers.py:193 #: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104 msgid "ID" msgstr "" #: apps/role_setting/api/role_setting.py:17 #: apps/role_setting/api/role_setting.py:23 #: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258 #: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235 msgid "Name" msgstr "用户名" #: apps/role_setting/api/role_setting.py:18 #: apps/role_setting/api/role_setting.py:29 #: apps/role_setting/serializers/role_setting_serializers.py:194 msgid "Enable" msgstr "启用" #: apps/role_setting/api/role_setting.py:26 msgid "Permission" msgstr "权限" #: apps/role_setting/api/role_setting.py:37 msgid "Children" msgstr "子级" #: apps/role_setting/api/role_setting.py:55 #: apps/role_setting/serializers/role_setting_serializers.py:107 msgid "Role type" msgstr "角色类型" #: apps/role_setting/api/role_setting.py:76 msgid "Internal role" msgstr "内置角色" #: apps/role_setting/api/role_setting.py:80 msgid "Custom role" msgstr "自定义角色" #: apps/role_setting/api/role_setting.py:108 #: apps/role_setting/api/role_setting.py:128 #: apps/role_setting/api/role_setting.py:164 #: apps/role_setting/serializers/role_setting_serializers.py:110 #: apps/role_setting/serializers/role_setting_serializers.py:329 #: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86 msgid "Role ID" msgstr "角色 ID" #: apps/role_setting/api/role_setting.py:135 msgid "User relation ID" msgstr "用户关系 ID" #: apps/role_setting/api/role_setting.py:145 #: apps/role_setting/api/role_setting.py:185 #: apps/role_setting/serializers/role_setting_serializers.py:330 #: apps/users/api/user.py:77 apps/users/serializers/login.py:27 #: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114 #: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124 #: apps/workspace/serializers/workspace_serializers.py:240 #: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105 #: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62 #: apps/xpack/serializers/chat_user.py:564 msgid "Username" msgstr "用户名" #: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84 #: apps/xpack/api/chat_user.py:106 msgid "Nickname" msgstr "姓名" #: apps/role_setting/api/role_setting.py:148 msgid "Workspace Name" msgstr "工作空间" #: apps/role_setting/serializers/role_setting_serializers.py:104 msgid "Role name" msgstr "角色名称" #: apps/role_setting/serializers/role_setting_serializers.py:122 #: apps/role_setting/serializers/role_setting_serializers.py:200 #: apps/role_setting/serializers/role_setting_serializers.py:325 #: apps/role_setting/serializers/role_setting_serializers.py:337 msgid "Role does not exist" msgstr "角色不存在" #: apps/role_setting/serializers/role_setting_serializers.py:124 #: apps/role_setting/serializers/role_setting_serializers.py:202 msgid "Cannot modify built-in role" msgstr "不能修改内置角色" #: apps/role_setting/serializers/role_setting_serializers.py:132 msgid "Role name already exists" msgstr "角色名称已存在" #: apps/role_setting/serializers/role_setting_serializers.py:152 msgid "Invalid role type" msgstr "无效的角色类型" #: apps/role_setting/serializers/role_setting_serializers.py:204 msgid "Cannot delete built-in role" msgstr "无法删除内置角色" #: apps/role_setting/serializers/role_setting_serializers.py:262 #: apps/users/api/user.py:135 apps/users/serializers/user.py:471 #: apps/workspace/serializers/workspace_serializers.py:161 #: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158 #: apps/xpack/serializers/chat_user.py:172 #: apps/xpack/serializers/chat_user.py:502 msgid "User IDs" msgstr "用户 ID" #: apps/role_setting/serializers/role_setting_serializers.py:267 #: apps/users/api/user.py:30 msgid "Workspace IDs" msgstr "工作空间 ID" #: apps/role_setting/serializers/role_setting_serializers.py:272 #: apps/workspace/serializers/workspace_serializers.py:172 msgid "Members" msgstr "成员集合" #: apps/role_setting/serializers/role_setting_serializers.py:312 #: apps/workspace/serializers/workspace_serializers.py:223 msgid "User relation does not exist" msgstr "用户关系不存在" #: apps/role_setting/serializers/role_setting_serializers.py:316 #: apps/workspace/serializers/workspace_serializers.py:226 msgid "Cannot remove member from built-in role" msgstr "不能从内置角色中移除成员" #: apps/role_setting/serializers/role_setting_serializers.py:370 msgid "Only update members to normal users" msgstr "只能为普通用户更新成员" #: apps/role_setting/views/role_setting.py:39 #: apps/role_setting/views/role_setting.py:40 #: apps/role_setting/views/role_setting.py:41 msgid "Get role permission template" msgstr "获取角色权限模板" #: apps/role_setting/views/role_setting.py:62 #: apps/role_setting/views/role_setting.py:63 #: apps/role_setting/views/role_setting.py:64 msgid "Create or update role" msgstr "创建或更新角色" #: apps/role_setting/views/role_setting.py:80 #: apps/role_setting/views/role_setting.py:81 #: apps/role_setting/views/role_setting.py:82 msgid "Get role list" msgstr "获取角色列表" #: apps/role_setting/views/role_setting.py:98 #: apps/role_setting/views/role_setting.py:99 #: apps/role_setting/views/role_setting.py:100 msgid "Delete role" msgstr "删除角色" #: apps/role_setting/views/role_setting.py:120 #: apps/role_setting/views/role_setting.py:121 #: apps/role_setting/views/role_setting.py:122 msgid "Create or update role permission" msgstr "创建或更新角色权限" #: apps/role_setting/views/role_setting.py:140 #: apps/role_setting/views/role_setting.py:141 #: apps/role_setting/views/role_setting.py:142 msgid "Get role permission" msgstr "获取角色权限" #: apps/role_setting/views/role_setting.py:161 #: apps/role_setting/views/role_setting.py:162 #: apps/role_setting/views/role_setting.py:163 msgid "Add member to system role" msgstr "系统角色添加成员" #: apps/role_setting/views/role_setting.py:186 #: apps/role_setting/views/role_setting.py:187 #: apps/role_setting/views/role_setting.py:188 msgid "Remove member from system role" msgstr "系统角色移除成员" #: apps/role_setting/views/role_setting.py:205 #: apps/role_setting/views/role_setting.py:206 #: apps/role_setting/views/role_setting.py:207 msgid "Get system role member list" msgstr "获取系统角色成员列表" #: apps/role_setting/views/role_setting.py:223 #: apps/role_setting/views/role_setting.py:224 #: apps/role_setting/views/role_setting.py:225 msgid "Get Workspace role list" msgstr "获取工作空间角色列表" #: apps/role_setting/views/role_setting.py:227 #: apps/role_setting/views/role_setting.py:248 #: apps/role_setting/views/role_setting.py:273 #: apps/role_setting/views/role_setting.py:292 msgid "Workspace Role" msgstr "工作空间角色" #: apps/role_setting/views/role_setting.py:242 #: apps/role_setting/views/role_setting.py:243 #: apps/role_setting/views/role_setting.py:244 msgid "Add member to workspace role" msgstr "工作空间角色添加成员" #: apps/role_setting/views/role_setting.py:268 #: apps/role_setting/views/role_setting.py:269 #: apps/role_setting/views/role_setting.py:270 msgid "Remove member from workspace role" msgstr "工作空间角色移除成员" #: apps/role_setting/views/role_setting.py:287 #: apps/role_setting/views/role_setting.py:288 #: apps/role_setting/views/role_setting.py:289 msgid "Get workspace role member list" msgstr "获取工作空间角色成员列表" #: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54 msgid "Folder token" msgstr "文件夹 Token" #: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46 #: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133 #: apps/shared/serializers/shared_tool.py:43 #: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142 #: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454 msgid "tool name" msgstr "工具名称" #: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53 #: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140 #: apps/shared/serializers/shared_tool.py:44 #: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144 #: apps/tools/serializers/tool.py:159 msgid "tool description" msgstr "工具描述" #: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184 #: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288 #: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66 #: apps/shared/serializers/shared_tool.py:107 #: apps/tools/serializers/tool.py:267 msgid "tool id" msgstr "工具 ID" #: apps/shared/serializers/shared_knowledge.py:35 #: apps/shared/serializers/shared_model.py:20 #: apps/shared/serializers/shared_tool.py:19 msgid "workspace id list" msgstr "工作空间ID" #: apps/shared/serializers/shared_knowledge.py:36 #: apps/shared/serializers/shared_model.py:21 #: apps/shared/serializers/shared_tool.py:20 msgid "authentication type" msgstr "认证类型" #: apps/shared/serializers/shared_knowledge.py:196 #: apps/shared/serializers/shared_knowledge.py:216 #: apps/shared/serializers/shared_knowledge.py:236 msgid "Knowledge does not exist" msgstr "知识库不存在" #: apps/shared/serializers/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:238 msgid "Only shared knowledge can be authorized" msgstr "仅限共享知识库可以被授权" #: apps/shared/serializers/shared_tool.py:74 msgid "Only shared tools can be deleted" msgstr "仅限共享工具可以被删除" #: apps/shared/serializers/shared_tool.py:76 msgid "System tools cannot be deleted" msgstr "系统工具不能被删除" #: apps/shared/serializers/shared_tool.py:118 #: apps/shared/serializers/shared_tool.py:138 msgid "Tool does not exist" msgstr "工具不存在" #: apps/shared/serializers/shared_tool.py:120 #: apps/shared/serializers/shared_tool.py:140 msgid "Only shared tools can be authorized" msgstr "仅限共享工具可以被授权" #: apps/shared/views/shared_dataset_lark_views.py:25 #: apps/shared/views/shared_dataset_lark_views.py:26 #: apps/shared/views/shared_dataset_lark_views.py:27 #: apps/xpack/views/dataset_lark_views.py:23 #: apps/xpack/views/dataset_lark_views.py:24 #: apps/xpack/views/dataset_lark_views.py:25 msgid "Create a lark knowledge base" msgstr "创建知识库" #: apps/shared/views/shared_dataset_lark_views.py:44 #: apps/shared/views/shared_dataset_lark_views.py:45 #: apps/shared/views/shared_dataset_lark_views.py:46 #: apps/xpack/views/dataset_lark_views.py:44 #: apps/xpack/views/dataset_lark_views.py:45 #: apps/xpack/views/dataset_lark_views.py:46 msgid "Update a lark knowledge base" msgstr "更新飞书知识库" #: apps/shared/views/shared_dataset_lark_views.py:67 #: apps/shared/views/shared_dataset_lark_views.py:68 #: apps/shared/views/shared_dataset_lark_views.py:69 #: apps/xpack/views/dataset_lark_views.py:67 #: apps/xpack/views/dataset_lark_views.py:68 #: apps/xpack/views/dataset_lark_views.py:69 msgid "Get document list from lark" msgstr "获取飞书文档列表" #: apps/shared/views/shared_dataset_lark_views.py:72 #: apps/shared/views/shared_dataset_lark_views.py:90 #: apps/shared/views/shared_dataset_lark_views.py:109 #: apps/shared/views/shared_dataset_lark_views.py:129 #: apps/shared/views/shared_document.py:36 #: apps/shared/views/shared_document.py:56 #: apps/shared/views/shared_document.py:83 #: apps/shared/views/shared_document.py:114 #: apps/shared/views/shared_document.py:131 #: apps/shared/views/shared_document.py:156 #: apps/shared/views/shared_document.py:184 #: apps/shared/views/shared_document.py:211 #: apps/shared/views/shared_document.py:238 #: apps/shared/views/shared_document.py:268 #: apps/shared/views/shared_document.py:297 #: apps/shared/views/shared_document.py:318 #: apps/shared/views/shared_document.py:346 #: apps/shared/views/shared_document.py:363 #: apps/shared/views/shared_document.py:384 #: apps/shared/views/shared_document.py:412 #: apps/shared/views/shared_document.py:436 #: apps/shared/views/shared_document.py:461 #: apps/shared/views/shared_document.py:486 #: apps/shared/views/shared_document.py:508 #: apps/shared/views/shared_document.py:531 #: apps/shared/views/shared_document.py:554 #: apps/shared/views/shared_document.py:575 #: apps/shared/views/shared_document.py:602 #: apps/shared/views/shared_document.py:629 #: apps/shared/views/shared_document.py:651 #: apps/shared/views/shared_document.py:665 msgid "Shared Knowledge/Documentation" msgstr "共享知识库/文档" #: apps/shared/views/shared_dataset_lark_views.py:85 #: apps/shared/views/shared_dataset_lark_views.py:86 #: apps/shared/views/shared_dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:86 #: apps/xpack/views/dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:88 msgid "Import documents to the lark knowledge base" msgstr "导入文档到飞书知识库" #: apps/shared/views/shared_dataset_lark_views.py:104 #: apps/shared/views/shared_dataset_lark_views.py:105 #: apps/shared/views/shared_dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:107 #: apps/xpack/views/dataset_lark_views.py:108 msgid "Synchronize lark document" msgstr "同步飞书文档" #: apps/shared/views/shared_dataset_lark_views.py:123 #: apps/shared/views/shared_dataset_lark_views.py:124 #: apps/shared/views/shared_dataset_lark_views.py:125 #: apps/xpack/views/dataset_lark_views.py:126 #: apps/xpack/views/dataset_lark_views.py:127 #: apps/xpack/views/dataset_lark_views.py:128 msgid "Batch synchronize lark document" msgstr "批量同步飞书文档" #: apps/shared/views/shared_document.py:30 #: apps/shared/views/shared_document.py:31 #: apps/shared/views/shared_document.py:32 msgid "Create shared document" msgstr "创建共享文档" #: apps/shared/views/shared_document.py:51 #: apps/shared/views/shared_document.py:52 #: apps/shared/views/shared_document.py:53 msgid "Get shared document" msgstr "获取共享文档" #: apps/shared/views/shared_document.py:77 #: apps/shared/views/shared_document.py:78 #: apps/shared/views/shared_document.py:79 msgid "Segmented shared document" msgstr "分段共享文档" #: apps/shared/views/shared_document.py:109 #: apps/shared/views/shared_document.py:110 #: apps/shared/views/shared_document.py:111 msgid "Get a list of shared segment IDs" msgstr "获取共享分段 ID 列表" #: apps/shared/views/shared_document.py:125 #: apps/shared/views/shared_document.py:126 #: apps/shared/views/shared_document.py:127 msgid "Cancel shared tasks in batches" msgstr "批量取消任务" #: apps/shared/views/shared_document.py:150 #: apps/shared/views/shared_document.py:151 #: apps/shared/views/shared_document.py:152 msgid "Create shared documents in batches" msgstr "批量创建共享文档" #: apps/shared/views/shared_document.py:178 #: apps/shared/views/shared_document.py:179 #: apps/shared/views/shared_document.py:180 msgid "Batch sync shared documents" msgstr "批量同步共享文档" #: apps/shared/views/shared_document.py:205 #: apps/shared/views/shared_document.py:206 #: apps/shared/views/shared_document.py:207 msgid "Delete shared documents in batches" msgstr "批量删除共享文档" #: apps/shared/views/shared_document.py:233 #: apps/shared/views/shared_document.py:234 msgid "Batch refresh shared document vector library" msgstr "批量刷新共享文档向量库" #: apps/shared/views/shared_document.py:262 #: apps/shared/views/shared_document.py:263 #: apps/shared/views/shared_document.py:264 msgid "Batch generate related shared problems" msgstr "批量生成相关问题" #: apps/shared/views/shared_document.py:291 #: apps/shared/views/shared_document.py:292 #: apps/shared/views/shared_document.py:293 msgid "Modify shared document hit processing methods in batches" msgstr "批量修改文档命中处理方法" #: apps/shared/views/shared_document.py:313 #: apps/shared/views/shared_document.py:314 msgid "Migrate shared documents in batches" msgstr "批量迁移共享文档" #: apps/shared/views/shared_document.py:341 #: apps/shared/views/shared_document.py:342 #: apps/shared/views/shared_document.py:343 msgid "Get shared document details" msgstr "文档文档详情" #: apps/shared/views/shared_document.py:357 #: apps/shared/views/shared_document.py:358 #: apps/shared/views/shared_document.py:359 msgid "Modify shared document" msgstr "修改共享文档" #: apps/shared/views/shared_document.py:379 #: apps/shared/views/shared_document.py:380 #: apps/shared/views/shared_document.py:381 msgid "Delete shared document" msgstr "删除共享文档" #: apps/shared/views/shared_document.py:406 #: apps/shared/views/shared_document.py:407 #: apps/shared/views/shared_document.py:408 msgid "Synchronize shared web site types" msgstr "同步共享网站类型" #: apps/shared/views/shared_document.py:430 #: apps/shared/views/shared_document.py:431 #: apps/shared/views/shared_document.py:432 msgid "Refresh shared document vector library" msgstr "刷新共享文档向量库" #: apps/shared/views/shared_document.py:455 #: apps/shared/views/shared_document.py:456 #: apps/shared/views/shared_document.py:457 msgid "Cancel shared task" msgstr "取消任务" #: apps/shared/views/shared_document.py:481 #: apps/shared/views/shared_document.py:482 #: apps/shared/views/shared_document.py:483 msgid "Get shared document by pagination" msgstr "获取共享文档分页列表" #: apps/shared/views/shared_document.py:504 #: apps/shared/views/shared_document.py:505 msgid "Export shared document" msgstr "导出共享文档" #: apps/shared/views/shared_document.py:527 #: apps/shared/views/shared_document.py:528 msgid "Export Zip shared document" msgstr "导出共享文档 Zip" #: apps/shared/views/shared_document.py:550 #: apps/shared/views/shared_document.py:551 msgid "Download shared source file" msgstr "下载共享源文件" #: apps/shared/views/shared_document.py:569 #: apps/shared/views/shared_document.py:571 msgid "Create Web site shared documents" msgstr "创建网站文档" #: apps/shared/views/shared_document.py:596 #: apps/shared/views/shared_document.py:597 #: apps/shared/views/shared_document.py:598 msgid "Import QA and create shared documentation" msgstr "导入问答并创建文档" #: apps/shared/views/shared_document.py:623 #: apps/shared/views/shared_document.py:624 #: apps/shared/views/shared_document.py:625 msgid "Import tables and create shared documents" msgstr "导入表格并创建文档" #: apps/shared/views/shared_document.py:647 #: apps/shared/views/shared_document.py:648 msgid "Get shared QA template" msgstr "获取共享问答模板" #: apps/shared/views/shared_document.py:661 #: apps/shared/views/shared_document.py:662 msgid "Get shared form template" msgstr "获取共享表格模板" #: apps/shared/views/shared_knowledge.py:28 #: apps/shared/views/shared_knowledge.py:29 #: apps/shared/views/shared_knowledge.py:30 msgid "Get share resource knowledge" msgstr "获取共享资源知识库" #: apps/shared/views/shared_knowledge.py:48 #: apps/shared/views/shared_knowledge.py:49 #: apps/shared/views/shared_knowledge.py:50 msgid "Get shared knowledge list by pagination" msgstr "获取共享知识库分页列表" #: apps/shared/views/shared_knowledge.py:70 #: apps/shared/views/shared_knowledge.py:71 #: apps/shared/views/shared_knowledge.py:72 msgid "Update shared knowledge" msgstr "更新共享知识库" #: apps/shared/views/shared_knowledge.py:86 #: apps/shared/views/shared_knowledge.py:87 #: apps/shared/views/shared_knowledge.py:88 msgid "Get shared knowledge" msgstr "获取共享知识库" #: apps/shared/views/shared_knowledge.py:101 #: apps/shared/views/shared_knowledge.py:102 #: apps/shared/views/shared_knowledge.py:103 msgid "Delete shared knowledge" msgstr "删除共享知识库" #: apps/shared/views/shared_knowledge.py:119 #: apps/shared/views/shared_knowledge.py:120 #: apps/shared/views/shared_knowledge.py:121 msgid "Synchronize the shared knowledge base of the website" msgstr "同步共享知识库网站" #: apps/shared/views/shared_knowledge.py:145 #: apps/shared/views/shared_knowledge.py:146 #: apps/shared/views/shared_knowledge.py:147 msgid "Shared Hit test list" msgstr "命中测试列表" #: apps/shared/views/shared_knowledge.py:172 #: apps/shared/views/shared_knowledge.py:173 #: apps/shared/views/shared_knowledge.py:174 msgid "Shared Re-vectorize" msgstr "重新向量化" #: apps/shared/views/shared_knowledge.py:192 #: apps/shared/views/shared_knowledge.py:193 msgid "Export shared knowledge base" msgstr "导出共享知识库" #: apps/shared/views/shared_knowledge.py:210 #: apps/shared/views/shared_knowledge.py:211 msgid "Export shared knowledge base containing images" msgstr "导出包含图片的共享知识库" #: apps/shared/views/shared_knowledge.py:229 #: apps/shared/views/shared_knowledge.py:230 #: apps/shared/views/shared_knowledge.py:231 msgid "Shared generate related" msgstr "生成相关" #: apps/shared/views/shared_knowledge.py:251 #: apps/shared/views/shared_knowledge.py:252 #: apps/shared/views/shared_knowledge.py:253 msgid "Get model for shared knowledge base" msgstr "获取共享知识库模型" #: apps/shared/views/shared_knowledge.py:271 #: apps/shared/views/shared_knowledge.py:272 #: apps/shared/views/shared_knowledge.py:273 msgid "Get embedding model for shared knowledge base" msgstr "获取共享知识库嵌入模型" #: apps/shared/views/shared_knowledge.py:291 #: apps/shared/views/shared_knowledge.py:293 msgid "Authorization knowledge workspace" msgstr "授权知识工作空间" #: apps/shared/views/shared_knowledge.py:292 msgid "Authorization knowledge workspace " msgstr "授权知识工作空间" #: apps/shared/views/shared_knowledge.py:307 #: apps/shared/views/shared_knowledge.py:308 #: apps/shared/views/shared_knowledge.py:309 msgid "Get Authorization knowledge workspace" msgstr "获取授权知识工作空间" #: apps/shared/views/shared_knowledge.py:326 #: apps/shared/views/shared_knowledge.py:327 #: apps/shared/views/shared_knowledge.py:328 msgid "Create shared base knowledge" msgstr "创建共享知识库" #: apps/shared/views/shared_knowledge.py:349 #: apps/shared/views/shared_knowledge.py:350 #: apps/shared/views/shared_knowledge.py:351 msgid "Create shared web knowledge" msgstr "创建 web 知识库" #: apps/shared/views/shared_knowledge.py:381 #: apps/shared/views/shared_knowledge.py:382 #: apps/shared/views/shared_knowledge.py:383 msgid "Get shared workspace knowledge" msgstr "获取共享工作空间知识库" #: apps/shared/views/shared_knowledge.py:402 #: apps/shared/views/shared_knowledge.py:403 #: apps/shared/views/shared_knowledge.py:404 msgid "Get shared workspace knowledge list by pagination" msgstr "获取共享工作空间知识库分页列表" #: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215 msgid "Authorization model workspace" msgstr "授权模型工作空间" #: apps/shared/views/shared_model.py:214 msgid "Authorization model workspace " msgstr "授权模型工作空间" #: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230 #: apps/shared/views/shared_model.py:231 msgid "Get Authorization model workspace" msgstr "获取授权模型工作空间" #: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249 #: apps/shared/views/shared_model.py:250 msgid "Get Share model by workspace id" msgstr "获取共享模型工作空间 ID" #: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266 #: apps/shared/views/shared_model.py:267 msgid "Get Share model by workspace id with pagination" msgstr "获取共享模型工作空间 ID 分页列表" #: apps/shared/views/shared_paragraph.py:24 #: apps/shared/views/shared_paragraph.py:25 #: apps/shared/views/shared_paragraph.py:26 msgid "Shared paragraph list" msgstr "共享段落列表" #: apps/shared/views/shared_paragraph.py:29 #: apps/shared/views/shared_paragraph.py:50 #: apps/shared/views/shared_paragraph.py:76 #: apps/shared/views/shared_paragraph.py:93 #: apps/shared/views/shared_paragraph.py:125 #: apps/shared/views/shared_paragraph.py:151 #: apps/shared/views/shared_paragraph.py:179 #: apps/shared/views/shared_paragraph.py:201 #: apps/shared/views/shared_paragraph.py:233 #: apps/shared/views/shared_paragraph.py:259 #: apps/shared/views/shared_paragraph.py:283 #: apps/shared/views/shared_paragraph.py:314 #: apps/shared/views/shared_paragraph.py:345 #: apps/shared/views/shared_paragraph.py:371 msgid "Shared Knowledge/Documentation/Paragraph" msgstr "共享知识库/文档/段落" #: apps/shared/views/shared_paragraph.py:45 #: apps/shared/views/shared_paragraph.py:46 msgid "Create shared paragraph" msgstr "创建段落" #: apps/shared/views/shared_paragraph.py:70 #: apps/shared/views/shared_paragraph.py:71 #: apps/shared/views/shared_paragraph.py:72 msgid "Batch shared paragraph" msgstr "批量关联段落" #: apps/shared/views/shared_paragraph.py:88 #: apps/shared/views/shared_paragraph.py:89 msgid "Migrate shared paragraphs in batches" msgstr "批量迁移共享段落" #: apps/shared/views/shared_paragraph.py:119 #: apps/shared/views/shared_paragraph.py:120 #: apps/shared/views/shared_paragraph.py:121 msgid "Batch generate shared related" msgstr "批量生成相关" #: apps/shared/views/shared_paragraph.py:145 #: apps/shared/views/shared_paragraph.py:146 #: apps/shared/views/shared_paragraph.py:147 msgid "Modify shared paragraph data" msgstr "修改段落数据" #: apps/shared/views/shared_paragraph.py:174 #: apps/shared/views/shared_paragraph.py:175 #: apps/shared/views/shared_paragraph.py:176 msgid "Get shared paragraph details" msgstr "获取段落详情" #: apps/shared/views/shared_paragraph.py:196 #: apps/shared/views/shared_paragraph.py:197 #: apps/shared/views/shared_paragraph.py:198 msgid "Delete shared paragraph" msgstr "删除段落" #: apps/shared/views/shared_paragraph.py:227 #: apps/shared/views/shared_paragraph.py:228 #: apps/shared/views/shared_paragraph.py:229 msgid "Add shared associated questions" msgstr "添加关联问题" #: apps/shared/views/shared_paragraph.py:254 #: apps/shared/views/shared_paragraph.py:255 #: apps/shared/views/shared_paragraph.py:256 msgid "Get a list of shared paragraph questions" msgstr "获取共享段落问题列表" #: apps/shared/views/shared_paragraph.py:277 #: apps/shared/views/shared_paragraph.py:278 #: apps/shared/views/shared_paragraph.py:279 msgid "Disassociation shared issue" msgstr "取消关联问题" #: apps/shared/views/shared_paragraph.py:308 #: apps/shared/views/shared_paragraph.py:309 #: apps/shared/views/shared_paragraph.py:310 msgid "Related shared questions" msgstr "关联问题" #: apps/shared/views/shared_paragraph.py:340 #: apps/shared/views/shared_paragraph.py:341 #: apps/shared/views/shared_paragraph.py:342 msgid "Get shared paragraph list by pagination" msgstr "获取段落列表" #: apps/shared/views/shared_problem.py:23 #: apps/shared/views/shared_problem.py:24 #: apps/shared/views/shared_problem.py:25 msgid "Shared question list" msgstr "问题列表" #: apps/shared/views/shared_problem.py:28 #: apps/shared/views/shared_problem.py:50 #: apps/shared/views/shared_problem.py:71 #: apps/shared/views/shared_problem.py:94 #: apps/shared/views/shared_problem.py:115 #: apps/shared/views/shared_problem.py:135 #: apps/shared/views/shared_problem.py:158 #: apps/shared/views/shared_problem.py:182 msgid "Shared Knowledge/Documentation/Paragraph/Question" msgstr "知识库/文档/段落/问题" #: apps/shared/views/shared_problem.py:44 #: apps/shared/views/shared_problem.py:45 #: apps/shared/views/shared_problem.py:46 msgid "Create shared question" msgstr "创建问题" #: apps/shared/views/shared_problem.py:66 #: apps/shared/views/shared_problem.py:67 #: apps/shared/views/shared_problem.py:68 msgid "Get a list of associated shared paragraphs" msgstr "获取关联段落列表" #: apps/shared/views/shared_problem.py:88 #: apps/shared/views/shared_problem.py:89 #: apps/shared/views/shared_problem.py:90 msgid "Batch associated shared paragraphs" msgstr "批量关联段落" #: apps/shared/views/shared_problem.py:109 #: apps/shared/views/shared_problem.py:110 #: apps/shared/views/shared_problem.py:111 msgid "Batch deletion shared issues" msgstr "批量删除问题" #: apps/shared/views/shared_problem.py:130 #: apps/shared/views/shared_problem.py:131 #: apps/shared/views/shared_problem.py:132 msgid "Delete shared question" msgstr "删除问题" #: apps/shared/views/shared_problem.py:152 #: apps/shared/views/shared_problem.py:153 #: apps/shared/views/shared_problem.py:154 msgid "Modify shared question" msgstr "修改问题" #: apps/shared/views/shared_problem.py:177 #: apps/shared/views/shared_problem.py:178 #: apps/shared/views/shared_problem.py:179 msgid "Get the list of shared questions by page" msgstr "分页获取问题列表" #: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26 #: apps/shared/views/shared_tool.py:27 msgid "Get share resource tool" msgstr "获取共享资源工具" #: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44 #: apps/shared/views/shared_tool.py:45 msgid "Create shared tool" msgstr "创建共享工具" #: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63 #: apps/shared/views/shared_tool.py:64 msgid "Update shared tool" msgstr "更新共享工具" #: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79 #: apps/shared/views/shared_tool.py:80 msgid "Get shared tool" msgstr "获取共享工具" #: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94 #: apps/shared/views/shared_tool.py:95 msgid "Delete shared tool" msgstr "删除共享工具" #: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112 #: apps/shared/views/shared_tool.py:113 msgid "Get shared tool list by pagination" msgstr "获取共享工具列表" #: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135 #: apps/shared/views/shared_tool.py:136 msgid "Import shared tool" msgstr "导入共享工具" #: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153 #: apps/shared/views/shared_tool.py:154 msgid "Export shared tool" msgstr "导出共享工具" #: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171 #: apps/shared/views/shared_tool.py:172 msgid "Debug shared Tool" msgstr "调试共享工具" #: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189 #: apps/shared/views/shared_tool.py:190 msgid "Check shared code" msgstr "检查代码" #: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213 #: apps/shared/views/shared_tool.py:214 msgid "Edit shared tool icon" msgstr "修改共享工具图标" #: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235 msgid "Authorization tool workspace" msgstr "授权工作空间工具" #: apps/shared/views/shared_tool.py:234 msgid "Authorization tool workspace " msgstr "授权工作空间工具" #: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250 #: apps/shared/views/shared_tool.py:251 msgid "Get Authorization tool workspace" msgstr "获取授权工作空间工具" #: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269 #: apps/shared/views/shared_tool.py:270 msgid "Get shared workspace tool" msgstr "获取共享工作空间工具" #: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290 #: apps/shared/views/shared_tool.py:291 msgid "Get shared workspace tool list by pagination" msgstr "获取共享工作空间工具分页列表" #: apps/system_manage/serializers/email_setting.py:28 msgid "SMTP host" msgstr "" #: apps/system_manage/serializers/email_setting.py:29 msgid "SMTP port" msgstr "" #: apps/system_manage/serializers/email_setting.py:30 #: apps/system_manage/serializers/email_setting.py:34 msgid "Sender's email" msgstr "发送者邮箱" #: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93 #: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57 #: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291 #: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25 #: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24 #: apps/xpack/serializers/chat_user.py:74 #: apps/xpack/serializers/chat_user.py:267 #: apps/xpack/serializers/chat_user.py:601 msgid "Password" msgstr "密码" #: apps/system_manage/serializers/email_setting.py:32 msgid "Whether to enable TLS" msgstr "是否启用 TLS" #: apps/system_manage/serializers/email_setting.py:33 msgid "Whether to enable SSL" msgstr "是否启用 SSL" #: apps/system_manage/serializers/email_setting.py:49 msgid "Email verification failed" msgstr "邮件认证失败" #: apps/system_manage/serializers/user_resource_permission.py:53 msgid "target id" msgstr "当前 ID" #: apps/system_manage/serializers/user_resource_permission.py:70 msgid "Non-existent application|knowledge base id[" msgstr "不存在的智能体|知识库 ID[" #: apps/system_manage/views/email_setting.py:50 #: apps/system_manage/views/email_setting.py:51 #: apps/system_manage/views/email_setting.py:52 msgid "Create or update email settings" msgstr "创建或更新邮件设置" #: apps/system_manage/views/email_setting.py:55 #: apps/system_manage/views/email_setting.py:70 #: apps/system_manage/views/email_setting.py:86 msgid "Email Settings" msgstr "邮箱设置" #: apps/system_manage/views/email_setting.py:66 #: apps/system_manage/views/email_setting.py:67 msgid "Test email settings" msgstr "测试邮箱设置" #: apps/system_manage/views/email_setting.py:82 #: apps/system_manage/views/email_setting.py:83 #: apps/system_manage/views/email_setting.py:84 msgid "Get email settings" msgstr "获取邮箱设置" #: apps/system_manage/views/system_profile.py:22 #: apps/system_manage/views/system_profile.py:23 msgid "Get MaxKB related information" msgstr "获取 MaxKB 相关信息" #: apps/system_manage/views/system_profile.py:25 msgid "System parameters" msgstr "系统参数" #: apps/system_manage/views/user_resource_permission.py:40 #: apps/system_manage/views/user_resource_permission.py:41 msgid "Obtain resource authorization list" msgstr "获取资源授权列表" #: apps/system_manage/views/user_resource_permission.py:44 #: apps/system_manage/views/user_resource_permission.py:60 msgid "Resources authorization" msgstr "资源授权" #: apps/system_manage/views/user_resource_permission.py:55 #: apps/system_manage/views/user_resource_permission.py:56 msgid "Modify the resource authorization list" msgstr "修改资源授权列表" #: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169 msgid "variable name" msgstr "变量名称" #: apps/tools/serializers/tool.py:122 msgid "fields only support string|int|dict|array|float" msgstr "字段仅支持字符串|整数|字典|数组|浮点数" #: apps/tools/serializers/tool.py:131 msgid "field name" msgstr "字段名称" #: apps/tools/serializers/tool.py:132 msgid "field label" msgstr "标签" #: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160 #: apps/tools/serializers/tool.py:174 msgid "tool content" msgstr "工具内容" #: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161 #: apps/tools/serializers/tool.py:175 msgid "input field list" msgstr "输入字段列表" #: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162 #: apps/tools/serializers/tool.py:176 msgid "init field list" msgstr "内置字段列表" #: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177 msgid "init params" msgstr "内置参数" #: apps/tools/serializers/tool.py:170 msgid "variable value" msgstr "变量名称" #: apps/tools/serializers/tool.py:182 msgid "function content" msgstr "工具内容" #: apps/tools/serializers/tool.py:238 msgid "field has no value set" msgstr "字段未设置值" #: apps/tools/serializers/tool.py:262 #, python-brace-format msgid "Field: {name} Type: {_type} Value: {value} Type conversion error" msgstr "字段:{name} 类型:{_type} 值:{value} 类型转换错误" #: apps/tools/serializers/tool.py:275 msgid "Tool not found" msgstr "工具不存在" #: apps/tools/serializers/tool.py:388 msgid "function ID" msgstr "工具 ID" #: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34 #: apps/tools/views/tool.py:35 msgid "Create tool" msgstr "创建工具" #: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57 #: apps/tools/views/tool.py:58 msgid "Get tool by folder" msgstr "通过文件夹获取工具" #: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78 #: apps/tools/views/tool.py:79 msgid "Debug Tool" msgstr "调试工具" #: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99 #: apps/tools/views/tool.py:100 msgid "Update tool" msgstr "更新工具" #: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123 #: apps/tools/views/tool.py:124 msgid "Get tool" msgstr "获取工具" #: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142 #: apps/tools/views/tool.py:143 msgid "Delete tool" msgstr "删除工具" #: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168 #: apps/tools/views/tool.py:169 msgid "Get tool list by pagination" msgstr "获取工具列表" #: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196 #: apps/tools/views/tool.py:197 msgid "Import tool" msgstr "导入工具" #: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219 #: apps/tools/views/tool.py:220 msgid "Export tool" msgstr "导出工具" #: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245 #: apps/tools/views/tool.py:246 msgid "Check code" msgstr "检查代码" #: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269 #: apps/tools/views/tool.py:270 msgid "Edit tool icon" msgstr "修改工具图标" #: apps/users/api/user.py:154 msgid "Email or Username" msgstr "邮箱或用户名" #: apps/users/api/user.py:224 msgid "Language" msgstr "语言" #: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69 msgid "captcha" msgstr "验证码" #: apps/users/serializers/login.py:50 #: apps/xpack/serializers/chat_user_serializer.py:120 msgid "Captcha code error or expiration" msgstr "验证码错误或过期" #: apps/users/serializers/login.py:55 #: apps/xpack/serializers/auth_config_serializer.py:192 #: apps/xpack/serializers/chat_user_serializer.py:125 #: apps/xpack/serializers/qr_login/qr_login.py:37 msgid "The user has been disabled, please contact the administrator!" msgstr "用户已被禁用,请联系管理员!" #: apps/users/serializers/user.py:47 msgid "Is Edit Password" msgstr "是否编辑密码" #: apps/users/serializers/user.py:48 msgid "permissions" msgstr "无权限访问" #: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106 #: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557 #: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107 #: apps/xpack/serializers/chat_user.py:54 #: apps/xpack/serializers/chat_user.py:227 msgid "Email" msgstr "邮箱" #: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140 #: apps/xpack/serializers/chat_user.py:88 msgid "Nick name" msgstr "姓名" #: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145 #: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108 #: apps/xpack/serializers/chat_user.py:93 #: apps/xpack/serializers/chat_user.py:240 msgid "Phone" msgstr "手机" #: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68 msgid "Username must be 4-64 characters long" msgstr "用户名必须为4-64个字符" #: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298 #: apps/xpack/serializers/chat_user.py:81 #: apps/xpack/serializers/chat_user.py:274 msgid "" "The password must be 6-20 characters long and must be a combination of " "letters, numbers, and special characters." msgstr "密码必须为6-20个字符,且必须包含大小写字母、数字和特殊字符。" #: apps/users/serializers/user.py:170 msgid "Email or username" msgstr "邮箱或用户名" #: apps/users/serializers/user.py:226 msgid "" "The community version supports up to 2 users. If you need more users, please " "contact us (https://fit2cloud.com/)." msgstr "" "社区版支持最多2个用户,如需更多用户,请联系我们(https://fit2cloud.com/)。" #: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31 #: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247 msgid "Is Active" msgstr "是否启用" #: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262 msgid "Nickname is already in use" msgstr "Nickname已被使用" #: apps/users/serializers/user.py:286 msgid "Email is already in use" msgstr "邮箱已被使用" #: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281 msgid "Re Password" msgstr "确认密码" #: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522 #: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286 #: apps/xpack/serializers/chat_user.py:606 #: apps/xpack/serializers/chat_user.py:613 msgid "" "The confirmation password must be 6-20 characters long and must be a " "combination of letters, numbers, and special characters." msgstr "确认密码必须为6-20个字符,且必须包含字母、数字和特殊字符。" #: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309 msgid "User does not exist" msgstr "用户不存在" #: apps/users/serializers/user.py:348 msgid "Unable to delete administrator" msgstr "无法删除管理员" #: apps/users/serializers/user.py:366 msgid "Cannot modify administrator status" msgstr "不能修改管理员状态" #: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164 #: apps/xpack/serializers/chat_user.py:192 #: apps/xpack/serializers/chat_user.py:512 msgid "User IDs cannot be empty" msgstr "用户 ID 不能为空" #: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608 msgid "Confirm Password" msgstr "确认密码" #: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647 #: apps/xpack/api/knowledge_lark.py:26 msgid "Type" msgstr "类型" #: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651 msgid "The type only supports register|reset_password" msgstr "类型仅支持 register|reset_password" #: apps/users/serializers/user.py:581 #, python-brace-format msgid "Do not send emails again within {seconds} seconds" msgstr "不要在 {seconds} 秒内再次发送邮件" #: apps/users/serializers/user.py:611 msgid "" "The email service has not been set up. Please contact the administrator to " "set up the email service in [Email Settings]." msgstr "邮箱服务尚未设置,请联系管理员在 [邮箱设置] 中设置邮箱服务。" #: apps/users/serializers/user.py:622 #, python-brace-format msgid "【Intelligent knowledge base question and answer system-{action}】" msgstr "【智能知识库问答系统-{action}】" #: apps/users/serializers/user.py:623 msgid "User registration" msgstr "用户注册" #: apps/users/serializers/user.py:623 apps/users/views/user.py:248 #: apps/users/views/user.py:249 apps/users/views/user.py:250 #: apps/users/views/user.py:283 apps/users/views/user.py:284 #: apps/users/views/user.py:285 msgid "Change password" msgstr "修改密码" #: apps/users/serializers/user.py:644 msgid "Verification code" msgstr "验证码" #: apps/users/serializers/user.py:672 msgid "language only support:" msgstr "语言仅支持:" #: apps/users/views/login.py:38 apps/users/views/login.py:39 #: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184 #: apps/xpack/views/chat_user_auth.py:185 #: apps/xpack/views/chat_user_auth.py:186 msgid "Log in" msgstr "登录" #: apps/users/views/login.py:55 apps/users/views/login.py:56 #: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203 #: apps/xpack/views/chat_user_auth.py:204 #: apps/xpack/views/chat_user_auth.py:205 msgid "Sign out" msgstr "登出" #: apps/users/views/user.py:60 apps/users/views/user.py:61 #: apps/users/views/user.py:62 apps/users/views/user.py:74 #: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359 #: apps/xpack/views/system_chat_user.py:360 #: apps/xpack/views/system_chat_user.py:361 msgid "Get current user information" msgstr "获取当前用户信息" #: apps/users/views/user.py:120 apps/users/views/user.py:121 #: apps/users/views/user.py:122 msgid "Get all user" msgstr "获取所有用户" #: apps/users/views/user.py:133 apps/users/views/user.py:134 #: apps/users/views/user.py:135 msgid "Get user list under workspace" msgstr "获取工作空间下用户列表" #: apps/users/views/user.py:147 apps/users/views/user.py:148 #: apps/users/views/user.py:149 msgid "Get user member under workspace" msgstr "获取工作空间下用户成员" #: apps/users/views/user.py:161 apps/users/views/user.py:162 #: apps/users/views/user.py:163 msgid "Create user" msgstr "创建用户" #: apps/users/views/user.py:177 apps/users/views/user.py:178 #: apps/users/views/user.py:179 msgid "Get default password" msgstr "获取默认密码" #: apps/users/views/user.py:190 apps/users/views/user.py:191 #: apps/users/views/user.py:192 msgid "Delete user" msgstr "删除用户" #: apps/users/views/user.py:203 apps/users/views/user.py:204 #: apps/users/views/user.py:205 msgid "Get user information" msgstr "获取用户信息" #: apps/users/views/user.py:214 apps/users/views/user.py:215 #: apps/users/views/user.py:216 msgid "Update user information" msgstr "更新当前用户信息" #: apps/users/views/user.py:232 apps/users/views/user.py:233 #: apps/users/views/user.py:234 msgid "Batch delete user" msgstr "批量删除用户" #: apps/users/views/user.py:266 apps/users/views/user.py:267 #: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168 #: apps/workspace/views/workspace_chat_user.py:169 #: apps/workspace/views/workspace_chat_user.py:170 #: apps/xpack/views/system_chat_user.py:197 #: apps/xpack/views/system_chat_user.py:198 #: apps/xpack/views/system_chat_user.py:199 msgid "Get user paginated list" msgstr "获取用户分页列表" #: apps/users/views/user.py:300 apps/users/views/user.py:301 #: apps/users/views/user.py:302 msgid "Send email" msgstr "发送邮件" #: apps/users/views/user.py:318 apps/users/views/user.py:319 #: apps/users/views/user.py:320 msgid "Check whether the verification code is correct" msgstr "检查验证码是否正确" #: apps/users/views/user.py:335 apps/users/views/user.py:336 #: apps/users/views/user.py:337 msgid "Send email to current user" msgstr "发送邮件给当前用户" #: apps/users/views/user.py:353 apps/users/views/user.py:354 #: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336 #: apps/xpack/views/system_chat_user.py:337 #: apps/xpack/views/system_chat_user.py:338 msgid "Modify current user password" msgstr "修改当前用户密码" #: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352 msgid "Failed to change password" msgstr "修改密码失败" #: apps/workspace/api/workspace.py:73 #: apps/workspace/serializers/workspace_serializers.py:213 msgid "User Relation ID" msgstr "用户关系 ID" #: apps/workspace/api/workspace.py:87 msgid "Role Name" msgstr "角色名称" #: apps/workspace/serializers/workspace_serializers.py:42 #: apps/workspace/serializers/workspace_serializers.py:95 #: apps/workspace/serializers/workspace_serializers.py:110 #: apps/workspace/serializers/workspace_serializers.py:177 #: apps/workspace/serializers/workspace_serializers.py:219 #: apps/workspace/serializers/workspace_serializers.py:246 msgid "Workspace does not exist" msgstr "工作空间不存在" #: apps/workspace/serializers/workspace_serializers.py:49 msgid "Workspace name already exists" msgstr "工作空间名称已存在" #: apps/workspace/serializers/workspace_serializers.py:97 #: apps/workspace/serializers/workspace_serializers.py:112 msgid "Default workspace cannot be deleted" msgstr "默认工作空间不能被删除" #: apps/workspace/serializers/workspace_serializers.py:122 msgid "Applications Resource" msgstr "智能体资源" #: apps/workspace/serializers/workspace_serializers.py:124 msgid "Knowledge Resource" msgstr "知识库资源" #: apps/workspace/serializers/workspace_serializers.py:130 msgid "This workspace contains %s, cannot be deleted." msgstr "该工作空间下存在 %s,不能被删除。" #: apps/workspace/serializers/workspace_serializers.py:166 msgid "Role IDs" msgstr "角色 IDs" #: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33 #: apps/workspace/views/workspace.py:34 msgid "Create or update workspace" msgstr "创建或更新工作空间" #: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46 #: apps/workspace/views/workspace.py:47 msgid "Get system workspace list" msgstr "获取系统工作空间列表" #: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59 #: apps/workspace/views/workspace.py:60 msgid "Delete workspace" msgstr "删除工作空间" #: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76 #: apps/workspace/views/workspace.py:77 msgid "Check workspace can it be deleted" msgstr "检查工作空间是否可以被删除" #: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96 #: apps/workspace/views/workspace.py:97 msgid "Add member to system workspace" msgstr "系统工作空间添加成员" #: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115 #: apps/workspace/views/workspace.py:116 msgid "Remove member from system workspace" msgstr "系统工作空间移除成员" #: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134 #: apps/workspace/views/workspace.py:135 msgid "Get system workspace member list" msgstr "获取系统工作空间成员列表" #: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152 #: apps/workspace/views/workspace.py:153 msgid "Get workspace list" msgstr "获取工作空间列表" #: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165 #: apps/workspace/views/workspace.py:166 msgid "Add member to workspace" msgstr "工作空间添加成员" #: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184 #: apps/workspace/views/workspace.py:185 msgid "Remove member from workspace" msgstr "工作空间移除成员" #: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203 #: apps/workspace/views/workspace.py:204 msgid "Get workspace member list" msgstr "获取工作空间成员列表" #: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220 #: apps/workspace/views/workspace.py:221 msgid "Get workspace list by current user" msgstr "获取当前用户工作空间列表" #: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233 #: apps/workspace/views/workspace.py:234 msgid "Get workspace list by user" msgstr "根据用户获取工作空间列表" #: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247 #: apps/workspace/views/workspace.py:248 msgid "Get current user role list" msgstr "获取当前用户角色列表" #: apps/workspace/views/workspace_chat_user.py:44 #: apps/workspace/views/workspace_chat_user.py:45 #: apps/workspace/views/workspace_chat_user.py:46 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/xpack/views/system_chat_user.py:57 #: apps/xpack/views/system_chat_user.py:58 #: apps/xpack/views/system_chat_user.py:59 msgid "Create chat user" msgstr "创建对话用户" #: apps/workspace/views/workspace_chat_user.py:47 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/workspace/views/workspace_chat_user.py:63 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/workspace/views/workspace_chat_user.py:76 #: apps/workspace/views/workspace_chat_user.py:87 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/workspace/views/workspace_chat_user.py:105 #: apps/workspace/views/workspace_chat_user.py:120 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/workspace/views/workspace_chat_user.py:136 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/workspace/views/workspace_chat_user.py:152 #: apps/workspace/views/workspace_chat_user.py:171 msgid "Workspace/Chat user" msgstr "工作空间/对话用户" #: apps/workspace/views/workspace_chat_user.py:60 #: apps/workspace/views/workspace_chat_user.py:61 #: apps/workspace/views/workspace_chat_user.py:62 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/xpack/views/system_chat_user.py:86 #: apps/xpack/views/system_chat_user.py:87 #: apps/xpack/views/system_chat_user.py:88 msgid "Delete chat user" msgstr "删除对话用户" #: apps/workspace/views/workspace_chat_user.py:73 #: apps/workspace/views/workspace_chat_user.py:74 #: apps/workspace/views/workspace_chat_user.py:75 #: apps/xpack/views/system_chat_user.py:99 #: apps/xpack/views/system_chat_user.py:100 #: apps/xpack/views/system_chat_user.py:101 msgid "Get chat user information" msgstr "获取对话用户信息" #: apps/workspace/views/workspace_chat_user.py:84 #: apps/workspace/views/workspace_chat_user.py:85 #: apps/workspace/views/workspace_chat_user.py:86 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/xpack/views/system_chat_user.py:110 #: apps/xpack/views/system_chat_user.py:111 #: apps/xpack/views/system_chat_user.py:112 msgid "Update chat user information" msgstr "更新对话用户信息" #: apps/workspace/views/workspace_chat_user.py:102 #: apps/workspace/views/workspace_chat_user.py:103 #: apps/workspace/views/workspace_chat_user.py:104 #: apps/workspace/views/workspace_chat_user.py:261 #: apps/workspace/views/workspace_chat_user.py:262 #: apps/workspace/views/workspace_chat_user.py:263 #: apps/xpack/views/system_chat_user.py:128 #: apps/xpack/views/system_chat_user.py:129 #: apps/xpack/views/system_chat_user.py:130 #: apps/xpack/views/system_chat_user.py:318 #: apps/xpack/views/system_chat_user.py:319 #: apps/xpack/views/system_chat_user.py:320 msgid "Get user list by group" msgstr "获取用户组列表" #: apps/workspace/views/workspace_chat_user.py:117 #: apps/workspace/views/workspace_chat_user.py:118 #: apps/workspace/views/workspace_chat_user.py:119 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/xpack/views/system_chat_user.py:143 #: apps/xpack/views/system_chat_user.py:144 #: apps/xpack/views/system_chat_user.py:145 msgid "Batch delete chat user" msgstr "批量删除对话用户" #: apps/workspace/views/workspace_chat_user.py:133 #: apps/workspace/views/workspace_chat_user.py:134 #: apps/workspace/views/workspace_chat_user.py:135 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/xpack/views/system_chat_user.py:160 #: apps/xpack/views/system_chat_user.py:161 #: apps/xpack/views/system_chat_user.py:162 msgid "Batch add chat user to group" msgstr "批量添加对话用户到用户组" #: apps/workspace/views/workspace_chat_user.py:149 #: apps/workspace/views/workspace_chat_user.py:150 #: apps/workspace/views/workspace_chat_user.py:151 #: apps/xpack/views/system_chat_user.py:177 #: apps/xpack/views/system_chat_user.py:178 #: apps/xpack/views/system_chat_user.py:179 msgid "Change chat user password" msgstr "修改对话用户密码" #: apps/workspace/views/workspace_chat_user.py:186 #: apps/workspace/views/workspace_chat_user.py:187 #: apps/workspace/views/workspace_chat_user.py:188 #: apps/xpack/views/system_chat_user.py:230 #: apps/xpack/views/system_chat_user.py:231 #: apps/xpack/views/system_chat_user.py:232 msgid "Create or update Chat User Group" msgstr "创建或更新对话用户组" #: apps/workspace/views/workspace_chat_user.py:191 #: apps/workspace/views/workspace_chat_user.py:202 #: apps/workspace/views/workspace_chat_user.py:216 #: apps/workspace/views/workspace_chat_user.py:232 #: apps/workspace/views/workspace_chat_user.py:249 #: apps/workspace/views/workspace_chat_user.py:264 msgid "Workspace/User Group" msgstr "工作空间/用户组" #: apps/workspace/views/workspace_chat_user.py:198 #: apps/workspace/views/workspace_chat_user.py:199 #: apps/workspace/views/workspace_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:243 #: apps/xpack/views/system_chat_user.py:244 #: apps/xpack/views/system_chat_user.py:245 msgid "Get user group list" msgstr "获取用户组列表" #: apps/workspace/views/workspace_chat_user.py:211 #: apps/workspace/views/workspace_chat_user.py:212 #: apps/workspace/views/workspace_chat_user.py:213 #: apps/xpack/views/system_chat_user.py:256 #: apps/xpack/views/system_chat_user.py:257 #: apps/xpack/views/system_chat_user.py:258 msgid "Delete chat user group" msgstr "删除对话用户组" #: apps/workspace/views/workspace_chat_user.py:226 #: apps/workspace/views/workspace_chat_user.py:227 #: apps/workspace/views/workspace_chat_user.py:228 #: apps/xpack/views/system_chat_user.py:273 #: apps/xpack/views/system_chat_user.py:274 #: apps/xpack/views/system_chat_user.py:275 msgid "Add member to chat user group" msgstr "添加成员到对话用户组" #: apps/workspace/views/workspace_chat_user.py:243 #: apps/workspace/views/workspace_chat_user.py:244 #: apps/workspace/views/workspace_chat_user.py:245 #: apps/xpack/views/system_chat_user.py:295 #: apps/xpack/views/system_chat_user.py:296 #: apps/xpack/views/system_chat_user.py:297 msgid "Remove member from chat user group" msgstr "从对话用户组移除成员" #: apps/xpack/api/auth_config.py:29 msgid "Auth Type" msgstr "认证类型" #: apps/xpack/api/auth_config.py:30 msgid "Config" msgstr "配置" #: apps/xpack/api/auth_config.py:77 msgid "Corp ID" msgstr "" #: apps/xpack/api/auth_config.py:78 msgid "Agent ID" msgstr "" #: apps/xpack/api/auth_config.py:79 msgid "App Secret" msgstr "" #: apps/xpack/api/auth_config.py:80 msgid "Callback URL" msgstr "" #: apps/xpack/api/auth_config.py:84 msgid "Key" msgstr "" #: apps/xpack/api/auth_config.py:106 msgid "Access Token" msgstr "" #: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83 #: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101 #: apps/xpack/serializers/chat_user.py:177 #: apps/xpack/serializers/chat_user.py:252 msgid "User Group IDs" msgstr "用户组 IDs" #: apps/xpack/api/chat_user.py:118 msgid "User Group Names" msgstr "用户组名称" #: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120 #: apps/xpack/serializers/resource_chat_user.py:37 msgid "Username or Nickname" msgstr "用户名或姓名" #: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360 msgid "Sync Type" msgstr "同步类型" #: apps/xpack/api/knowledge_lark.py:25 msgid "Token" msgstr "令牌" #: apps/xpack/api/knowledge_lark.py:27 msgid "Is Exist" msgstr "是否存在" #: apps/xpack/api/license.py:13 msgid "corporation" msgstr "公司" #: apps/xpack/api/license.py:14 msgid "isv" msgstr "" #: apps/xpack/api/license.py:15 msgid "expired" msgstr "过期时间" #: apps/xpack/api/license.py:16 msgid "product" msgstr "产品" #: apps/xpack/api/license.py:17 msgid "edition" msgstr "版本" #: apps/xpack/api/license.py:18 msgid "license version" msgstr "license 版本" #: apps/xpack/api/license.py:19 msgid "count" msgstr "数量" #: apps/xpack/api/license.py:20 msgid "serial number" msgstr "序列号" #: apps/xpack/api/license.py:21 msgid "remark" msgstr "备注" #: apps/xpack/api/license.py:26 msgid "message" msgstr "消息" #: apps/xpack/api/license.py:27 msgid "license details" msgstr "license 详情" #: apps/xpack/api/license.py:36 #: apps/xpack/serializers/license/license_serializers.py:56 msgid "license file" msgstr "license 文件" #: apps/xpack/api/license.py:37 msgid "License file is required" msgstr "license 文件是必需的" #: apps/xpack/api/license.py:38 msgid "Invalid license file format" msgstr "无效的 license 文件格式" #: apps/xpack/api/operate_log.py:12 #: apps/xpack/serializers/operate_log_serializer.py:57 msgid "menu" msgstr "菜单" #: apps/xpack/api/operate_log.py:13 #: apps/xpack/serializers/operate_log_serializer.py:58 msgid "operate" msgstr "操作" #: apps/xpack/api/operate_log.py:14 msgid "menu_label" msgstr "菜单标签" #: apps/xpack/api/operate_log.py:15 msgid "operate_label" msgstr "操作标签" #: apps/xpack/api/platform.py:35 msgid "Platform type" msgstr "平台类型" #: apps/xpack/api/platform.py:50 msgid "Platform configuration" msgstr "平台配置" #: apps/xpack/api/resource_chat_user_group.py:40 #: apps/xpack/serializers/resource_chat_user.py:25 #: apps/xpack/serializers/resource_chat_user_group.py:69 msgid "is auth" msgstr "是否认证" #: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99 msgid "User Group ID" msgstr "用户组 ID" #: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406 #: apps/xpack/serializers/chat_user.py:563 msgid "Group ID" msgstr "用户组 ID" #: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541 msgid "User group relation IDs" msgstr "用户组关系 ID" #: apps/xpack/serializers/application_setting_serializer.py:19 msgid "theme color" msgstr "主题颜色" #: apps/xpack/serializers/application_setting_serializer.py:21 msgid "header font color" msgstr "标题字体颜色" #: apps/xpack/serializers/application_setting_serializer.py:25 msgid "float location type" msgstr "浮动位置类型" #: apps/xpack/serializers/application_setting_serializer.py:26 msgid "float location value" msgstr "浮动位置值" #: apps/xpack/serializers/application_setting_serializer.py:30 msgid "float location x" msgstr "浮动位置 X" #: apps/xpack/serializers/application_setting_serializer.py:31 msgid "float location y" msgstr "浮动位置 Y" #: apps/xpack/serializers/application_setting_serializer.py:35 msgid "show source" msgstr "显示来源" #: apps/xpack/serializers/application_setting_serializer.py:36 msgid "show exec" msgstr "显示执行" #: apps/xpack/serializers/application_setting_serializer.py:38 msgid "show history" msgstr "显示历史" #: apps/xpack/serializers/application_setting_serializer.py:39 msgid "draggable" msgstr "是否可拖动" #: apps/xpack/serializers/application_setting_serializer.py:40 msgid "show guide" msgstr "显示论坛" #: apps/xpack/serializers/application_setting_serializer.py:42 msgid "icon url" msgstr "图标" #: apps/xpack/serializers/application_setting_serializer.py:43 msgid "chat background" msgstr "聊天背景" #: apps/xpack/serializers/application_setting_serializer.py:44 msgid "chat background url" msgstr "聊天背景地址" #: apps/xpack/serializers/application_setting_serializer.py:45 msgid "avatar" msgstr "头像" #: apps/xpack/serializers/application_setting_serializer.py:46 msgid "avatar url" msgstr "头像地址" #: apps/xpack/serializers/application_setting_serializer.py:47 msgid "user avatar" msgstr "用户头像" #: apps/xpack/serializers/application_setting_serializer.py:48 msgid "user avatar url" msgstr "用户头像地址" #: apps/xpack/serializers/application_setting_serializer.py:49 msgid "float icon" msgstr "浮动图标" #: apps/xpack/serializers/application_setting_serializer.py:50 msgid "float icon url" msgstr "浮动图标地址" #: apps/xpack/serializers/application_setting_serializer.py:51 msgid "disclaimer" msgstr "免责申明" #: apps/xpack/serializers/application_setting_serializer.py:52 msgid "disclaimer value" msgstr "免责申明内容" #: apps/xpack/serializers/application_setting_serializer.py:55 msgid "show avatar" msgstr "显示头像" #: apps/xpack/serializers/application_setting_serializer.py:56 msgid "show user avatar" msgstr "显示用户头像" #: apps/xpack/serializers/application_setting_serializer.py:124 msgid "Float location field type error" msgstr "浮动位置字段类型错误" #: apps/xpack/serializers/application_setting_serializer.py:130 msgid "Custom theme field type error" msgstr "自定义主题字段类型错误" #: apps/xpack/serializers/auth_config_serializer.py:27 #: apps/xpack/serializers/platform_serializer.py:31 msgid "App Secret is required" msgstr "App Secret 是必填项" #: apps/xpack/serializers/auth_config_serializer.py:28 #: apps/xpack/serializers/platform_serializer.py:26 #: apps/xpack/serializers/platform_serializer.py:34 #: apps/xpack/serializers/platform_serializer.py:40 #: apps/xpack/serializers/platform_serializer.py:46 msgid "Callback URL is required" msgstr "Callback URL 是必填项" #: apps/xpack/serializers/auth_config_serializer.py:32 #: apps/xpack/serializers/auth_config_serializer.py:41 msgid "Corp ID is required" msgstr "Corp ID 是必填项" #: apps/xpack/serializers/auth_config_serializer.py:33 #: apps/xpack/serializers/platform_serializer.py:22 msgid "Agent ID is required" msgstr "Agent ID 是必填项" #: apps/xpack/serializers/auth_config_serializer.py:37 #: apps/xpack/serializers/auth_config_serializer.py:42 msgid "App Key is required" msgstr "App Key 是必填项" #: apps/xpack/serializers/auth_config_serializer.py:53 msgid "LDAP server cannot be empty" msgstr "LDAP server不能为空" #: apps/xpack/serializers/auth_config_serializer.py:54 msgid "Base DN cannot be empty" msgstr "Base DN不能为空" #: apps/xpack/serializers/auth_config_serializer.py:55 msgid "Password cannot be empty" msgstr "密码不能为空" #: apps/xpack/serializers/auth_config_serializer.py:56 msgid "OU cannot be empty" msgstr "OU不能为空" #: apps/xpack/serializers/auth_config_serializer.py:57 msgid "LDAP filter cannot be empty" msgstr "LDAP过滤器不能为空" #: apps/xpack/serializers/auth_config_serializer.py:58 msgid "LDAP mapping cannot be empty" msgstr "LDAP映射不能为空" #: apps/xpack/serializers/auth_config_serializer.py:62 msgid "Authorization address cannot be empty" msgstr "认证地址不能为空" #: apps/xpack/serializers/auth_config_serializer.py:63 msgid "Token address cannot be empty" msgstr "令牌地址不能为空" #: apps/xpack/serializers/auth_config_serializer.py:64 msgid "User information address cannot be empty" msgstr "用户信息地址不能为空" #: apps/xpack/serializers/auth_config_serializer.py:65 msgid "Scope cannot be empty" msgstr "范围不能为空" #: apps/xpack/serializers/auth_config_serializer.py:66 msgid "Client ID cannot be empty" msgstr "客户端 ID 不能为空" #: apps/xpack/serializers/auth_config_serializer.py:67 msgid "Client secret cannot be empty" msgstr "客户端密钥不能为空" #: apps/xpack/serializers/auth_config_serializer.py:68 msgid "Redirect address cannot be empty" msgstr "重定向地址不能为空" #: apps/xpack/serializers/auth_config_serializer.py:69 msgid "Field mapping cannot be empty" msgstr "字段映射不能为空" #: apps/xpack/serializers/auth_config_serializer.py:262 msgid "Configuration information is wrong and failed to save" msgstr "配置信息错误,保存失败" #: apps/xpack/serializers/auth_config_serializer.py:288 msgid "Connection failed" msgstr "连接失败" #: apps/xpack/serializers/auth_config_serializer.py:306 msgid "Platform does not exist" msgstr "平台不存在" #: apps/xpack/serializers/auth_config_serializer.py:316 msgid "Unsupported platform type" msgstr "不支持的平台类型" #: apps/xpack/serializers/channel/chat_manage.py:100 msgid "Think: " msgstr "思考内容: " #: apps/xpack/serializers/channel/chat_manage.py:103 #: apps/xpack/serializers/channel/chat_manage.py:105 msgid "AI reply: " msgstr "AI 回复: " #: apps/xpack/serializers/channel/chat_manage.py:318 msgid "Thinking, please wait a moment!" msgstr "思考中,请稍等!" #: apps/xpack/serializers/channel/ding_talk.py:19 #: apps/xpack/serializers/channel/wechat.py:91 #: apps/xpack/serializers/channel/wechat.py:132 #: apps/xpack/serializers/channel/wecom.py:78 #: apps/xpack/serializers/channel/wecom.py:259 msgid "The corresponding platform configuration was not found" msgstr "未找到对应的平台配置" #: apps/xpack/serializers/channel/ding_talk.py:27 #: apps/xpack/serializers/channel/lark.py:117 msgid "Currently only text messages are supported" msgstr "目前仅支持文本消息" #: apps/xpack/serializers/channel/ding_talk.py:91 #: apps/xpack/serializers/channel/wechat.py:163 #: apps/xpack/serializers/channel/wecom.py:189 msgid "Image download failed, check network" msgstr "图片下载失败,请检查网络" #: apps/xpack/serializers/channel/ding_talk.py:92 #: apps/xpack/serializers/channel/wechat.py:161 #: apps/xpack/serializers/channel/wecom.py:185 msgid "Please analyze the content of the image." msgstr "请分析图片内容。" #: apps/xpack/serializers/channel/ding_talk.py:95 msgid "DingTalk application: {user}" msgstr "钉钉智能体: {user}" #: apps/xpack/serializers/channel/ding_talk.py:106 #: apps/xpack/serializers/channel/ding_talk.py:151 msgid "Content generated by AI" msgstr "AI 生成的内容" #: apps/xpack/serializers/channel/lark.py:92 msgid "Lark application: " msgstr "飞书智能体: " #: apps/xpack/serializers/channel/slack.py:116 msgid "The corresponding platform configuration for Slack was not found" msgstr "Slack 的对应平台配置未找到" #: apps/xpack/serializers/channel/slack.py:206 msgid "Thinking..." msgstr "思考中..." #: apps/xpack/serializers/channel/slack.py:333 msgid "Invalid json format." msgstr "json 格式无效。" #: apps/xpack/serializers/channel/slack.py:339 msgid "Invalid Slack request" msgstr "Slack 请求无效" #: apps/xpack/serializers/channel/slack.py:347 msgid "Slack application: {user}" msgstr "Slack 智能体: {user}" #: apps/xpack/serializers/channel/slack.py:480 msgid "Stop" msgstr "停止" #: apps/xpack/serializers/channel/tools.py:58 #, python-brace-format msgid "" "Thinking about 【{question}】...If you want me to continue answering, please " "reply {trigger_message}" msgstr "思考【{question}】...如果你想让我继续回答,请回复 {trigger_message}" #: apps/xpack/serializers/channel/tools.py:158 msgid "" "\n" " ------------\n" "[To be continued, reply \"Continue to answer the question]" msgstr "" "\n" "------------\n" "[待续,回复 \"继续回答问题]" #: apps/xpack/serializers/channel/tools.py:238 #, python-brace-format msgid "" "To be continued, reply \"{trigger_message}\" to continue answering the " "question" msgstr "待续,回复 \"{trigger_message}\" 继续回答问题" #: apps/xpack/serializers/channel/wechat.py:143 #, python-brace-format msgid "WeChat Official Account: {account}" msgstr "微信公众账号: {account}" #: apps/xpack/serializers/channel/wechat.py:150 #: apps/xpack/serializers/channel/wecom.py:171 #: apps/xpack/serializers/channel/wecom.py:175 msgid "" "The app does not enable the speech-to-text function or the speech-to-text " "function fails." msgstr "智能体未开启语音转文字功能或语音转文字功能失败。" #: apps/xpack/serializers/channel/wechat.py:189 msgid "Message types not supported yet" msgstr "消息类型暂不支持" #: apps/xpack/serializers/channel/wechat.py:196 msgid "Welcome to subscribe" msgstr "欢迎订阅" #: apps/xpack/serializers/channel/wecom.py:86 msgid "Enterprise WeChat user: " msgstr "企业微信用户: " #: apps/xpack/serializers/channel/wecom.py:97 msgid "Enterprise WeChat customer service: " msgstr "企业微信客服: " #: apps/xpack/serializers/channel/wecom.py:134 #: apps/xpack/serializers/channel/wecom.py:150 msgid "This type of message is not supported yet" msgstr "此类型消息暂不支持" #: apps/xpack/serializers/channel/wecom.py:254 msgid "Signature missing" msgstr "签名缺失" #: apps/xpack/serializers/channel/wecom.py:266 #: apps/xpack/serializers/channel/wecom.py:273 #, python-brace-format msgid "An error occurred while processing the GET request {e}" msgstr "get 请求处理时发生错误 {e}" #: apps/xpack/serializers/chat_auth.py:51 msgid "The password is incorrect" msgstr "密码不正确" #: apps/xpack/serializers/chat_user.py:42 msgid "Some user groups do not exist" msgstr "某些用户组不存在" #: apps/xpack/serializers/chat_user.py:181 msgid "Is Append" msgstr "是否追加" #: apps/xpack/serializers/chat_user.py:194 msgid "User Group IDs cannot be empty" msgstr "用户组 IDs 不能为空" #: apps/xpack/serializers/chat_user.py:198 msgid "Some users do not exist" msgstr "某些用户不存在" #: apps/xpack/serializers/chat_user.py:361 msgid "Sync Type: LOCAL or LDAP" msgstr "同步类型: LOCAL 或 LDAP" #: apps/xpack/serializers/chat_user.py:403 msgid "Unsupported sync type" msgstr "不支持的同步类型" #: apps/xpack/serializers/chat_user.py:412 #: apps/xpack/serializers/chat_user.py:444 #: apps/xpack/serializers/chat_user.py:483 #: apps/xpack/serializers/chat_user.py:510 #: apps/xpack/serializers/chat_user.py:548 #: apps/xpack/serializers/chat_user.py:570 msgid "User group does not exist" msgstr "用户组不存在" #: apps/xpack/serializers/chat_user.py:451 msgid "User group name already exists" msgstr "用户组名称已存在" #: apps/xpack/serializers/chat_user.py:485 msgid "Default user group cannot be deleted" msgstr "默认用户组不能被删除" #: apps/xpack/serializers/chat_user.py:550 msgid "User group relation IDs cannot be empty" msgstr "用户组关系 IDs 不能为空" #: apps/xpack/serializers/chat_user_serializer.py:75 msgid "Invalid access token" msgstr "无效的访问令牌" #: apps/xpack/serializers/chat_user_serializer.py:102 msgid "The user does not have permission to access the application" msgstr "用户没有访问智能体的权限" #: apps/xpack/serializers/dataset_lark_serializer.py:56 #: apps/xpack/serializers/dataset_lark_serializer.py:299 msgid "app id" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:57 #: apps/xpack/serializers/dataset_lark_serializer.py:300 msgid "app secret" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:58 #: apps/xpack/serializers/dataset_lark_serializer.py:105 #: apps/xpack/serializers/dataset_lark_serializer.py:301 msgid "folder token" msgstr "文件夹令牌" #: apps/xpack/serializers/dataset_lark_serializer.py:60 msgid "embedding model" msgstr "向量模型" #: apps/xpack/serializers/dataset_lark_serializer.py:71 #: apps/xpack/serializers/dataset_lark_serializer.py:311 msgid "Network error or folder token error!" msgstr "网络错误或文件夹令牌错误!" #: apps/xpack/serializers/dataset_lark_serializer.py:113 #: apps/xpack/serializers/dataset_lark_serializer.py:155 #: apps/xpack/task/sync.py:308 msgid "Knowledge base not found!" msgstr "知识库未找到!" #: apps/xpack/serializers/dataset_lark_serializer.py:125 #: apps/xpack/task/sync.py:240 msgid "Failed to get lark document list!" msgstr "获取飞书文档列表失败!" #: apps/xpack/serializers/dataset_lark_serializer.py:147 msgid "Knowledge id" msgstr "知识库 ID" #: apps/xpack/serializers/dataset_lark_serializer.py:169 msgid "Synchronization is only supported for lark documents" msgstr "仅支持飞书文档的同步" #: apps/xpack/serializers/license/license_serializers.py:102 #: apps/xpack/serializers/license/license_serializers.py:123 #: apps/xpack/serializers/license/license_tools.py:111 msgid "The license is invalid" msgstr "许可证无效" #: apps/xpack/serializers/license/license_tools.py:136 msgid "License usage limit exceeded." msgstr "License 使用限制已超出。" #: apps/xpack/serializers/license/license_tools.py:160 msgid "The network is busy, try again later." msgstr "网络繁忙,请稍后再试。" #: apps/xpack/serializers/operate_log_serializer.py:59 msgid "user" msgstr "用户" #: apps/xpack/serializers/operate_log_serializer.py:61 msgid "ip_address" msgstr "IP 地址" #: apps/xpack/serializers/operate_log_serializer.py:62 msgid "workspace_id" msgstr "工作空间ID" #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Fail" msgstr "失败" #: apps/xpack/serializers/operate_log_serializer.py:171 msgid "Menu" msgstr "菜单" #: apps/xpack/serializers/operate_log_serializer.py:172 msgid "Operate" msgstr "操作" #: apps/xpack/serializers/operate_log_serializer.py:173 msgid "Operate user" msgstr "操作用户" #: apps/xpack/serializers/operate_log_serializer.py:175 msgid "Ip Address" msgstr "IP地址" #: apps/xpack/serializers/operate_log_serializer.py:176 msgid "API Details" msgstr "API详情" #: apps/xpack/serializers/operate_log_serializer.py:177 msgid "Operate Time" msgstr "操作时间" #: apps/xpack/serializers/platform_serializer.py:12 msgid "app_id is required" msgstr "app_id 是必填项" #: apps/xpack/serializers/platform_serializer.py:13 msgid "app_secret is required" msgstr "app_secret 是必填项" #: apps/xpack/serializers/platform_serializer.py:14 msgid "token is required" msgstr "token 是必填项" #: apps/xpack/serializers/platform_serializer.py:15 msgid "callback_url is required" msgstr "callback_url 是必填项" #: apps/xpack/serializers/platform_serializer.py:21 #: apps/xpack/serializers/platform_serializer.py:30 msgid "App ID is required" msgstr "App ID 是必填项" #: apps/xpack/serializers/platform_serializer.py:23 msgid "Secret is required" msgstr "Secret 是必填项" #: apps/xpack/serializers/platform_serializer.py:24 msgid "Token is required" msgstr "Token 是必填项" #: apps/xpack/serializers/platform_serializer.py:33 msgid "Verification Token is required" msgstr "验证令牌是必填项" #: apps/xpack/serializers/platform_serializer.py:38 msgid "Client ID is required" msgstr "Client ID 是必填项" #: apps/xpack/serializers/platform_serializer.py:39 msgid "Client Secret is required" msgstr "客户端密钥是必填项" #: apps/xpack/serializers/platform_serializer.py:44 msgid "Signing Secret is required" msgstr "签名密钥是必填项" #: apps/xpack/serializers/platform_serializer.py:45 msgid "Bot User Token is required" msgstr "机器人用户令牌是必填项" #: apps/xpack/serializers/platform_serializer.py:66 msgid "Check if the fields are correct" msgstr "检查字段是否正确" #: apps/xpack/serializers/platform_serializer.py:155 #, python-brace-format msgid "The platform configuration corresponding to {type} was not found" msgstr "未找到对应 {type} 的平台配置" #: apps/xpack/serializers/resource_chat_user.py:35 #: apps/xpack/serializers/resource_chat_user.py:111 #: apps/xpack/serializers/resource_chat_user_group.py:18 #: apps/xpack/serializers/resource_chat_user_group.py:86 msgid "Resource id" msgstr "资源ID" #: apps/xpack/serializers/resource_chat_user.py:38 #: apps/xpack/serializers/resource_chat_user.py:112 msgid "User group id" msgstr "用户组ID" #: apps/xpack/serializers/resource_chat_user.py:94 msgid "Is auth" msgstr "是否授权" #: apps/xpack/serializers/resource_chat_user_group.py:20 msgid "User group name" msgstr "用户名" #: apps/xpack/serializers/resource_chat_user_group.py:68 msgid "user_group_id" msgstr "用户组ID" #: apps/xpack/serializers/sso_auth/cas.py:32 msgid "HttpClient query failed: " msgstr "HttpClient 查询失败: " #: apps/xpack/serializers/sso_auth/cas.py:58 msgid "CAS authentication failed" msgstr "CAS 认证失败" #: apps/xpack/serializers/sso_auth/oauth2.py:165 #: apps/xpack/serializers/sso_auth/oauth2.py:184 #: apps/xpack/serializers/sso_auth/oauth2.py:187 msgid "Failed to obtain user information" msgstr "获取用户信息失败" #: apps/xpack/serializers/system_api_key.py:12 msgid "Allow cross domain" msgstr "允许跨域" #: apps/xpack/serializers/system_api_key.py:13 msgid "Cross domain list" msgstr "跨域列表" #: apps/xpack/serializers/system_api_key.py:44 msgid "system API key id" msgstr "系统 API 密钥 ID" #: apps/xpack/serializers/system_params.py:20 msgid "theme" msgstr "主题" #: apps/xpack/serializers/system_params.py:22 msgid "login logo" msgstr "登录 logo" #: apps/xpack/serializers/system_params.py:23 msgid "login image" msgstr "登录图片" #: apps/xpack/serializers/system_params.py:24 msgid "title" msgstr "标题" #: apps/xpack/serializers/system_params.py:25 msgid "slogan" msgstr "标语" #: apps/xpack/serializers/system_params.py:26 #: apps/xpack/serializers/system_params.py:27 msgid "show user manual" msgstr "显示用户手册" #: apps/xpack/serializers/system_params.py:28 msgid "user manual url" msgstr "用户手册网址" #: apps/xpack/serializers/system_params.py:29 msgid "show forum" msgstr "显示论坛" #: apps/xpack/serializers/system_params.py:30 msgid "forum url" msgstr "论坛网址" #: apps/xpack/serializers/system_params.py:31 msgid "show project" msgstr "显示项目" #: apps/xpack/serializers/system_params.py:32 msgid "project url" msgstr "项目网址" #: apps/xpack/views/application_setting.py:24 #: apps/xpack/views/application_setting.py:25 #: apps/xpack/views/application_setting.py:26 msgid "Modify Application Settings" msgstr "修改智能体设置" #: apps/xpack/views/application_setting.py:42 #: apps/xpack/views/application_setting.py:43 #: apps/xpack/views/application_setting.py:44 msgid "Get Application Settings" msgstr "获取智能体设置" #: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53 #: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46 #: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48 msgid "Get authentication types" msgstr "获取认证类型" #: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70 #: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108 #: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235 #: apps/xpack/views/auth.py:249 msgid "Authentication Configuration" msgstr "认证配置" #: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68 #: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62 #: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64 msgid "Test LDAP connection" msgstr "测试 LDAP 连接" #: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88 #: apps/xpack/views/auth.py:89 msgid "Add or modify authentication configuration" msgstr "添加或修改认证配置" #: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106 #: apps/xpack/views/auth.py:107 msgid "Get authentication configuration" msgstr "获取认证配置" #: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119 #: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112 #: apps/xpack/views/chat_user_auth.py:113 #: apps/xpack/views/chat_user_auth.py:114 msgid "Ldap Log in" msgstr "LDAP 登录" #: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138 #: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176 #: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208 #: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286 #: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327 #: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368 msgid "Three-party login" msgstr "三方登录" #: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136 #: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129 #: apps/xpack/views/chat_user_auth.py:130 #: apps/xpack/views/chat_user_auth.py:131 msgid "CAS Log in" msgstr "CAS 登录" #: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155 #: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148 #: apps/xpack/views/chat_user_auth.py:149 #: apps/xpack/views/chat_user_auth.py:150 msgid "OIDC Log in" msgstr "OIDC 登录" #: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174 #: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167 #: apps/xpack/views/chat_user_auth.py:168 #: apps/xpack/views/chat_user_auth.py:169 msgid "OAuth2 Log in" msgstr "OAuth2 登录" #: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192 #: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220 #: apps/xpack/views/chat_user_auth.py:221 #: apps/xpack/views/chat_user_auth.py:222 msgid "Scan code login type" msgstr "扫码登录类型" #: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206 #: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220 #: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222 #: apps/xpack/views/chat_user_auth.py:234 #: apps/xpack/views/chat_user_auth.py:235 #: apps/xpack/views/chat_user_auth.py:236 #: apps/xpack/views/chat_user_auth.py:249 #: apps/xpack/views/chat_user_auth.py:250 #: apps/xpack/views/chat_user_auth.py:251 msgid "Get platform information" msgstr "获取平台信息" #: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233 #: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261 #: apps/xpack/views/chat_user_auth.py:262 #: apps/xpack/views/chat_user_auth.py:263 msgid "Modify platform information" msgstr "修改平台信息" #: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247 #: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275 #: apps/xpack/views/chat_user_auth.py:276 #: apps/xpack/views/chat_user_auth.py:277 msgid "Test platform connection" msgstr "测试平台连接" #: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264 #: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292 #: apps/xpack/views/chat_user_auth.py:293 #: apps/xpack/views/chat_user_auth.py:294 msgid "DingTalk callback" msgstr "钉钉回调" #: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284 #: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312 #: apps/xpack/views/chat_user_auth.py:313 #: apps/xpack/views/chat_user_auth.py:314 msgid "DingTalk OAuth2 callback" msgstr "钉钉 OAuth2 回调" #: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305 #: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333 #: apps/xpack/views/chat_user_auth.py:334 #: apps/xpack/views/chat_user_auth.py:335 msgid "WeCom callback" msgstr "企业微信回调" #: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325 #: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353 #: apps/xpack/views/chat_user_auth.py:354 #: apps/xpack/views/chat_user_auth.py:355 msgid "WeCom OAuth2 callback" msgstr "企业微信 OAuth2 回调" #: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346 #: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374 #: apps/xpack/views/chat_user_auth.py:375 #: apps/xpack/views/chat_user_auth.py:376 msgid "Lark callback" msgstr "飞书回调" #: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366 #: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394 #: apps/xpack/views/chat_user_auth.py:395 #: apps/xpack/views/chat_user_auth.py:396 msgid "Lark OAuth2 callback" msgstr "飞书 OAuth2 回调" #: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65 #: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252 #: apps/xpack/views/chat_user_auth.py:264 #: apps/xpack/views/chat_user_auth.py:278 msgid "Chat User/Authentication Configuration" msgstr "对话用户/认证配置" #: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83 #: apps/xpack/views/chat_user_auth.py:84 msgid "Add or modify Chat/Authentication Configuration" msgstr "添加或修改对话/认证配置" #: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100 #: apps/xpack/views/chat_user_auth.py:101 msgid "Get Authentication Configuration" msgstr "获取认证配置" #: apps/xpack/views/chat_user_auth.py:102 msgid "Chat User/login authentication" msgstr "对话用户/登录认证" #: apps/xpack/views/chat_user_auth.py:115 #: apps/xpack/views/chat_user_auth.py:132 #: apps/xpack/views/chat_user_auth.py:151 #: apps/xpack/views/chat_user_auth.py:170 #: apps/xpack/views/chat_user_auth.py:223 #: apps/xpack/views/chat_user_auth.py:237 #: apps/xpack/views/chat_user_auth.py:295 #: apps/xpack/views/chat_user_auth.py:315 #: apps/xpack/views/chat_user_auth.py:336 #: apps/xpack/views/chat_user_auth.py:356 #: apps/xpack/views/chat_user_auth.py:377 #: apps/xpack/views/chat_user_auth.py:397 msgid "Chat User/Three-party login" msgstr "对话用户/三方登录" #: apps/xpack/views/chat_user_auth.py:187 msgid "Chat User/login" msgstr "对话用户/登录" #: apps/xpack/views/chat_user_auth.py:414 #: apps/xpack/views/chat_user_auth.py:415 #: apps/xpack/views/chat_user_auth.py:416 msgid "Application Password Certification" msgstr "智能体密码认证" #: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32 #: apps/xpack/views/license.py:33 msgid "Get license information" msgstr "获取许可证信息" #: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44 msgid "Update license information" msgstr "更新许可证信息" #: apps/xpack/views/license.py:43 msgid "Update license information by uploading a new license file" msgstr "通过上传新许可证文件更新许可证信息" #: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22 #: apps/xpack/views/operate_log.py:23 msgid "Get menu operate log" msgstr "获取菜单操作日志" #: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41 #: apps/xpack/views/operate_log.py:57 msgid "System operate log" msgstr "系统操作日志" #: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37 #: apps/xpack/views/operate_log.py:38 msgid "Get paginated operate log" msgstr "获取分页操作日志" #: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55 #: apps/xpack/views/operate_log.py:56 msgid "Export operate log" msgstr "导出操作日志" #: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61 #: apps/xpack/views/platform.py:62 msgid "Get platform configuration" msgstr "获取平台配置" #: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79 msgid "Application/application access" msgstr "智能体/智能体访问" #: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74 #: apps/xpack/views/platform.py:75 msgid "Update platform configuration" msgstr "更新平台配置" #: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95 #: apps/xpack/views/platform.py:96 msgid "Get platform status" msgstr "获取平台状态" #: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118 msgid "Application/Get platform status" msgstr "智能体/获取平台状态" #: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114 #: apps/xpack/views/platform.py:115 msgid "Update platform status" msgstr "更新平台状态" #: apps/xpack/views/resource_chat_user.py:27 #: apps/xpack/views/resource_chat_user.py:28 #: apps/xpack/views/resource_chat_user.py:29 msgid "Get Resource chat user List" msgstr "获取资源对话用户列表" #: apps/xpack/views/resource_chat_user.py:32 #: apps/xpack/views/resource_chat_user.py:54 #: apps/xpack/views/resource_chat_user.py:77 #: apps/xpack/views/system_chat_user_group.py:24 #: apps/xpack/views/system_chat_user_group.py:45 #: apps/xpack/views/system_chat_user_group.py:67 msgid "Chat user" msgstr "对话用户" #: apps/xpack/views/resource_chat_user.py:48 #: apps/xpack/views/resource_chat_user.py:49 #: apps/xpack/views/resource_chat_user.py:50 msgid "Edit Resource chat user List" msgstr "编辑资源对话用户列表" #: apps/xpack/views/resource_chat_user.py:72 #: apps/xpack/views/resource_chat_user.py:73 #: apps/xpack/views/resource_chat_user.py:74 msgid "Get Resource chat user page List" msgstr "获取资源对话用户分页列表" #: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20 #: apps/xpack/views/system_api_key.py:21 msgid "Create SystemAPIKey" msgstr "创建系统 API 密钥" #: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35 #: apps/xpack/views/system_api_key.py:36 msgid "Get SystemAPIKey List" msgstr "获取系统 API 密钥列表" #: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51 #: apps/xpack/views/system_api_key.py:52 msgid "Update SystemAPIKey" msgstr "更新系统 API 密钥" #: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67 #: apps/xpack/views/system_api_key.py:68 msgid "Delete SystemAPIKey" msgstr "删除系统 API 密钥" #: apps/xpack/views/system_chat_user.py:60 #: apps/xpack/views/system_chat_user.py:76 #: apps/xpack/views/system_chat_user.py:89 #: apps/xpack/views/system_chat_user.py:102 #: apps/xpack/views/system_chat_user.py:113 #: apps/xpack/views/system_chat_user.py:131 #: apps/xpack/views/system_chat_user.py:146 #: apps/xpack/views/system_chat_user.py:163 #: apps/xpack/views/system_chat_user.py:180 #: apps/xpack/views/system_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:217 msgid "System/Chat user" msgstr "系统/对话用户" #: apps/xpack/views/system_chat_user.py:73 #: apps/xpack/views/system_chat_user.py:74 #: apps/xpack/views/system_chat_user.py:75 msgid "Get chat user list" msgstr "获取对话用户列表" #: apps/xpack/views/system_chat_user.py:214 #: apps/xpack/views/system_chat_user.py:216 msgid "Sync chat users" msgstr "同步对话用户" #: apps/xpack/views/system_chat_user.py:215 msgid "Sync chat users from external source" msgstr "从外部源同步对话用户" #: apps/xpack/views/system_chat_user.py:235 #: apps/xpack/views/system_chat_user.py:247 #: apps/xpack/views/system_chat_user.py:261 #: apps/xpack/views/system_chat_user.py:279 #: apps/xpack/views/system_chat_user.py:301 #: apps/xpack/views/system_chat_user.py:321 msgid "System/User Group" msgstr "系统/用户组" #: apps/xpack/views/system_chat_user_group.py:19 #: apps/xpack/views/system_chat_user_group.py:20 #: apps/xpack/views/system_chat_user_group.py:21 msgid "Get Resource chat user group List" msgstr "获取资源对话用户组列表" #: apps/xpack/views/system_chat_user_group.py:39 #: apps/xpack/views/system_chat_user_group.py:40 #: apps/xpack/views/system_chat_user_group.py:41 msgid "Edit Resource chat user group List" msgstr "编辑资源对话用户组列表" #: apps/xpack/views/system_chat_user_group.py:62 #: apps/xpack/views/system_chat_user_group.py:64 msgid "Get Resource chat user group page List" msgstr "获取资源对话用户组分页列表" #: apps/xpack/views/system_chat_user_group.py:63 msgid "Get Resource chat user page group List" msgstr "获取资源对话用户分页组列表" #: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23 #: apps/xpack/views/system_params.py:24 msgid "View appearance settings" msgstr "查看外观设置" #: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40 #: apps/xpack/views/system_params.py:41 msgid "Update appearance settings" msgstr "更新外观设置" msgid "Application Access" msgstr "智能体接入" msgid "Display execution details" msgstr "是否显示执行详情" msgid "LOCAL" msgstr "账号登录" msgid "CAS" msgstr "CAS" msgid "LDAP" msgstr "LDAP" msgid "OIDC" msgstr "OIDC" msgid "OAuth2" msgstr "OAuth2" msgid "dingtalk" msgstr "钉钉" msgid "wecom" msgstr "企业微信" msgid "lark" msgstr "飞书" msgid "Get tool list" msgstr "获取工具列表" msgid "Setting" msgstr "设置" msgid "Get verification results" msgstr "获取验证结果" msgid "Validation" msgstr "验证" msgid "Models Resource" msgstr "模型资源" msgid "Tools Resource" msgstr "工具资源" msgid "Get resource model list" msgstr "获取资源管理模型列表" msgid "System Model" msgstr "系统模型" msgid "Dialogue users" msgstr "对话用户" msgid "Conversation log" msgstr "对话日志" msgid "Public access link" msgstr "公共访问链接" msgid "User management" msgstr "用户管理" msgid "Chat User/logout" msgstr "对话用户/登出" msgid "Paragraph" msgstr "段落" msgid "User group" msgstr "用户组" msgid "Remove member from user group" msgstr "从用户组中移除成员" msgid "Create a web site knowledge base" msgstr "创建一个web知识库" msgid "Modify knowledge base information" msgstr "修改知识库信息" msgid "Delete knowledge base" msgstr "删除知识库" msgid "model" msgstr "模型" msgid "Batch add user to group" msgstr "批量添加用户到组" msgid "Add internal tool" msgstr "添加内置工具" msgid "Batch generate related" msgstr "批量生成相关" msgid "Update personal system API_KEY" msgstr "更新个人系统 API KEY" msgid "Delete user group" msgstr "删除用户组" msgid "Add user" msgstr "添加用户" msgid "folder" msgstr "文件夹" msgid "Create or update user group" msgstr "创建或更新用户组" msgid "Edit folder" msgstr "编辑文件夹" msgid "Email settings" msgstr "邮箱设置" msgid "trial listening" msgstr "试听" msgid "Add member to user group" msgstr "添加成员到用户组" msgid "System" msgstr "系统" msgid "Shared Knowledge/Document" msgstr "共享知识库/文档" msgid "System Application" msgstr "系统智能体" msgid "Hit-Test" msgstr "命中测试" msgid "Export Application" msgstr "导出智能体" msgid "Add ApiKey" msgstr "添加 API KEY" msgid "Delete application API_KEY" msgstr "删除智能体 API KEY" msgid "knowledge Base" msgstr "知识库" msgid "API KEY" msgstr "API KEY" msgid "Download" msgstr "下载" msgid "User" msgstr "用户" msgid "Delete personal system API_KEY" msgstr "删除个人系统API KEY" msgid "Add personal system API_KEY" msgstr "添加个人系统API KEY" msgid "Generate related documents" msgstr "生成相关文档" msgid "Modify application access token" msgstr "修改智能体程序访问令牌" msgid "File not exist. Only manually uploaded documents are supported" msgstr "文件不存在, 仅支持手动上传的文档" msgid "Resource" msgstr "资源管理" msgid "LDAP configuration not found or not active" msgstr "LDAP 配置未找到或未激活" msgid "Lark configuration not found or not active" msgstr "飞书配置未找到或未激活" msgid "Failed to get Lark collaborators" msgstr "获取飞书协作者失败" msgid "Failed to get Lark user details" msgstr "获取飞书用户详情失败" msgid "WeCom configuration not found or not active" msgstr "企业微信配置未找到或未激活" msgid "Failed to get WeCom access token" msgstr "获取企业微信访问令牌失败" msgid "Failed to get WeCom agent info" msgstr "获取企业微信代理信息失败" msgid "Failed to get WeCom department users" msgstr "获取企业微信部门用户失败" msgid "Failed to get WeCom user info" msgstr "获取企业微信用户信息失败" msgid "Publish status" msgstr "发布状态" msgid "Unpublished" msgstr "未发布" msgid "Published" msgstr "已发布" msgid "users_permission" msgstr "用户权限" msgid "Get user authorization status of resource" msgstr "获取资源对用户的授权状态" msgid "Edit user authorization status of resource" msgstr "修改资源对用户的授权状态" msgid "Get user authorization status of resource by page" msgstr "分页获取资源对用户的授权状态" msgid "Obtain resource authorization list by page" msgstr "分页获取资源授权列表" msgid "Engine model type" msgstr "引擎模型类型" msgid "If not passed, the default value is 16k_zh (Chinese universal)" msgstr "如果未传递,默认值为 16k_zh(中文通用)" msgid "Chinese telephone universal" msgstr "中文电话通用" msgid "English telephone universal" msgstr "英文电话通用" msgid "Commonly used in Chinese" msgstr "中文常用" msgid "Chinese, English, and Guangdong" msgstr "中文、英文和广东话" msgid "Chinese medical" msgstr "中文医疗" msgid "English" msgstr "英文" msgid "Cantonese" msgstr "粤语" msgid "Japanese" msgstr "日语" msgid "Korean" msgstr "韩语" msgid "Vietnamese" msgstr "越南语" msgid "Malay language" msgstr "马来语" msgid "Indonesian language" msgstr "印尼语" msgid "Filipino language" msgstr "菲律宾语" msgid "Thai" msgstr "泰语" msgid "Portuguese" msgstr "葡萄牙语" msgid "Turkish" msgstr "土耳其语" msgid "Arabic" msgstr "阿拉伯语" msgid "Spanish" msgstr "西班牙语" msgid "Hindi" msgstr "印地语" msgid "French" msgstr "法语" msgid "German" msgstr "德语" msgid "Multiple dialects, supporting 23 dialects" msgstr "多种方言,支持 23 种方言" msgid "This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects." msgstr "本接口用于识别 60 秒之内的短音频文件。支持中文普通话、英语、粤语、日语、越南语、马来语、印度尼西亚语、菲律宾语、泰语、葡萄牙语、土耳其语、阿拉伯语、印地语、法语、德语及 23 种汉语方言。" msgid "CueWord" msgstr "提示词" msgid "If not passed, the default value is What is this audio saying? Only answer the audio content" msgstr "如果未传递,默认值为 这段音频在说什么,只回答音频的内容" msgid "The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text." msgstr "Qwen-Omni 系列模型支持输入多种模态的数据,包括视频、音频、图片、文本,并输出音频与文本" msgid "resource authorization" msgstr "资源授权" msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition." msgstr "基于Qwen-Audio的端到端语音识别大模型,支持3分钟以内的音频识别,目前主要支持中英文识别。" msgid "If not passed, the default value is 'zh'" msgstr "如果未传递,则默认值为'zh'" msgid "System resources authorization" msgstr "系统资源授权" msgid "This folder contains resources that you dont have permission" msgstr "此文件夹包含您没有权限的资源" msgid "Text to Video" msgstr "文生视频" msgid "Image to Video" msgstr "图生视频" msgid "Authentication failed. Please verify that the parameters are correct" msgstr "认证失败,请检查参数是否正确" msgid "Chat context" msgstr "聊天上下文" msgid "Prompt template" msgstr "提示词模板" msgid "generate prompt" msgstr "生成提示词" msgid "Watermark" msgstr "水印" msgid "Whether to add watermark" msgstr "是否添加水印" msgid "Resolution" msgstr "分辨率" msgid "Ratio" msgstr "比例" msgid "Duration" msgstr "时长" msgid "Failed to generate video" msgstr "生成视频失败" msgid "password" msgstr "密码登录" msgid "Failed to obtain the image" msgstr "获取图片失败" msgid "Update auth setting" msgstr "更新认证设置" msgid "If not passed, the default value is streaming_asr_demo" msgstr "如果未传入,则默认值为 streaming_asr_demo" msgid "If not passed, the default value is 16000" msgstr "如果未传入,则默认值为 16000" msgid "Sample Rate" msgstr "采样率" msgid "Captcha is required" msgstr "验证码是必填项" msgid "Tag" msgstr "标签管理" msgid "Tag Setting" msgstr "标签设置" msgid "Download Original Document" msgstr "下载原文档" msgid "Replace Original Document" msgstr "替换原文档" msgid "Update License" msgstr "更新许可证" msgid "Tag Key" msgstr "标签" msgid "Tag Value" msgstr "标签值" msgid "Tag id does not exist" msgstr "标签 ID 不存在" msgid "Tag key already exists" msgstr "标签已存在" msgid "Tag value already exists" msgstr "标签值已存在" msgid "Non-existent id" msgstr "不存在的ID" msgid "No permission for the target folder" msgstr "没有目标文件夹的权限" msgid "Application token usage statistics" msgstr "智能体令牌使用统计" msgid "Application top question statistics" msgstr "智能体提问次数统计" msgid "SAML2 Metadata" msgstr "SAML2 元数据" msgid "SAML2 Log in" msgstr "SAML2 登录" msgid "SAML2 SSO" msgstr "SAML2 单点登录" msgid "Workflow" msgstr "工作流" msgid "Web source url" msgstr "Web 根地址" msgid "Web knowledge selector" msgstr "选择器" msgid "The default is body, you can enter .classname/#idname/tagname" msgstr "默认为 body,可输入 .classname/#idname/tagname" msgid "Please enter the Web root address" msgstr "请输入 Web 根地址" msgid "File size exceeds limit" msgstr "文件大小超出限制" msgid "File upload is not enabled" msgstr "文件上传未启用" msgid "Blocked unsafe redirect to internal host" msgstr "阻止不安全的重定向到内部主机" msgid "Audio file recognition - Tongyi Qwen" msgstr "录音文件识别-通义千问" msgid "Real-time speech recognition - Fun-ASR/Paraformer" msgstr "实时语音识别-Fun-ASR/Paraformer" msgid "Qwen-Omni" msgstr "多模态" msgid "Super-humanoid: Lingxiaoxuan Flow" msgstr "聆小璇" msgid "Super-humanoid: Lingyuyan Flow" msgstr "聆玉言" msgid "Super-humanoid: Lingfeiyi Flow" msgstr "聆飞逸" msgid "Super-humanoid: Lingxiaoyue Flow" msgstr "聆小玥" msgid "Super-humanoid: Sun Dasheng Flow" msgstr "孙大圣" msgid "Super-humanoid: Lingyuzhao Flow" msgstr "聆玉昭" msgid "Super-humanoid: Lingxiaotang Flow" msgstr "聆小糖" msgid "Super-humanoid: Lingxiaorong Flow" msgstr "聆小蓉" msgid "Super-humanoid: Xinyun Flow" msgstr "心云" msgid "Super-humanoid: Grant (EN)" msgstr "Grant" msgid "Super-humanoid: Lila (EN)" msgstr "Lila" msgid "Super-humanoid: Lingwanwan Pro" msgstr "聆万万" msgid "Super-humanoid: Yiyi Pro" msgstr "依依" msgid "Super-humanoid: Huifangnv Pro" msgstr "惠芳女" msgid "Super-humanoid: Lingxiaoying Pro" msgstr "聆小颖" msgid "Super-humanoid: Lingfeibo Pro" msgstr "聆飞博" msgid "Super-humanoid: Lingyuyan Pro" msgstr "聆玉言" msgid "Login failed %s times, account will be locked, you have %s more chances !" msgstr "登录失败 %s 次,账号将被锁定,您还有 %s 次机会!" msgid "This account has been locked for %s minutes, please try again later" msgstr "该账号已被锁定 %s 分钟,请稍后再试" msgid "User does not have permission to use API Key" msgstr "用户没有使用 API Key 的权限" msgid "Import knowledge workflow" msgstr "导入知识工作流" msgid "Export knowledge workflow" msgstr "导出知识工作流" msgid "Role IDs cannot be empty" msgstr "角色 ID 不能为空" msgid "Some roles do not exist" msgstr "部分角色不存在" msgid "Authorized pagination list for obtaining resources" msgstr "获取资源的关系分页列表" msgid "Resources mapping" msgstr "资源映射" msgid "Batch set user roles" msgstr "批量设置用户角色" msgid "Role Setting cannot be empty" msgstr "角色设置不能为空" msgid "View related resources" msgstr "查看关联资源" msgid "Feedback reason" msgstr "反馈理由" msgid "Other reason content" msgstr "其他反馈理由内容" msgid "accurate" msgstr "内容准确" msgid "complete" msgstr "内容完善" msgid "inaccurate" msgstr "内容不准确" msgid "incomplete" msgstr "内容不完善" msgid "Secret key is invalid" msgstr "密钥无效" msgid "Secret key is expired" msgstr "密钥已过期" msgid "Online Usage" msgstr "线上使用" msgid "API Call" msgstr "API 调用" msgid "Enterprise WeChat" msgstr "企业微信应用" msgid "WeChat Public Account" msgstr "微信公众号" msgid "Lark" msgstr "飞书应用" msgid "DingTalk" msgstr "钉钉应用" msgid "Enterprise WeChat Robot" msgstr "企业微信机器人" msgid "Trigger" msgstr "触发器" msgid "Slack" msgstr "Slack 应用" msgid "Root Directory" msgstr "根目录" msgid "Create trigger" msgstr "创建触发器" msgid "Get the trigger list" msgstr "获取触发器列表" msgid "Get trigger details" msgstr "获取触发器详情" msgid "Modify the trigger" msgstr "修改触发器" msgid "Delete the trigger" msgstr "删除触发器" msgid "Activate trigger in batches" msgstr "批量启用/禁用触发器" msgid "Get the trigger list by page" msgstr "分页获取触发器列表" msgid "Create trigger in source" msgstr "资源端创建触发器" msgid "Delete trigger in batches" msgstr "批量删除触发器" msgid "Get the trigger list of source" msgstr "获取资源端触发器列表" msgid "Get Task source trigger details" msgstr "获取资源端触发器详情" msgid "Delete the task source trigger" msgstr "删除资源端触发器" msgid "Get the task list of triggers" msgstr "获取触发器任务列表" msgid "Retrieve detailed records of tasks executed by the trigger." msgstr "获取由该触发器执行的任务详细记录。" msgid "Get a paginated list of execution records for trigger tasks." msgstr "获取触发器任务执行记录的分页列表。" msgid "%s must be an array" msgstr "%s 必须是数组类型" msgid "%s must not be empty" msgstr "%s 不能为空" msgid "%s values must be between %s and %s" msgstr "%s 的值必须在 %s 到 %s 之间" msgid "Invalid time format: %s, must be HH:MM (e.g., 09:00)" msgstr "时间格式无效: %s,必须是 HH:MM 格式 (例如: 09:00)" msgid "schedule_type must be one of %s" msgstr "schedule_type 必须是以下值之一: %s" msgid "interval_value must be an integer greater than or equal to 1" msgstr "interval_value 必须是大于或等于 1 的整数" msgid "interval_unit must be one of %s" msgstr "interval_unit 必须是以下值之一: %s" msgid "body must be an array" msgstr "body 必须是数组类型" msgid "Error trigger type" msgstr "触发器类型错误" msgid "The following id does not exist: %s" msgstr "以下 id 不存在: %s" msgid "%s must be a dict" msgstr "%s 必须是字典类型" msgid "input_field_list must be a dict" msgstr "input_field_list 必须是字典类型" msgid "%s type requires %s field" msgstr "%s 类型需要 %s 字段" msgid "trigger name" msgstr "触发器名称" msgid "trigger description" msgstr "触发器描述" msgid "trigger setting" msgstr "触发器设置" msgid "Trigger ID" msgstr "触发器ID" msgid "Trigger task can not be empty" msgstr "触发器任务不能为空" msgid "%s id does not exist" msgstr "%s id 不存在" msgid "Trigger id does not exist" msgstr "触发器 id 不存在" msgid "Trigger not found" msgstr "未找到触发器" msgid "Trigger must have at least one task" msgstr "触发器必须至少有一个任务" msgid "Trigger task number must be one" msgstr "触发器任务数量必须为一个" msgid "Incorrect trigger task" msgstr "触发器任务不正确" msgid "Trigger task ID" msgstr "触发器任务ID" msgid "Trigger task record ID" msgstr "触发器任务记录ID" msgid "Trigger task record id does not exist" msgstr "触发器任务记录 id 不存在" msgid "Order field" msgstr "排序字段" msgid "System Trigger" msgstr "系统触发器" msgid "Get the System trigger list of source" msgstr "获取来源的系统触发器列表" msgid "Get System Task source trigger details" msgstr "获取系统任务来源触发器详情" msgid "Modify the System task source trigger" msgstr "修改系统任务来源触发器" msgid "Modify the task source trigger" msgstr "修改任务来源触发器" msgid "Delete the System task source trigger" msgstr "删除系统任务来源触发器" msgid "Invalid source type" msgstr "无效的来源类型" msgid "Shared tool is not supported" msgstr "不支持共享工具" msgid "Read Trigger" msgstr "查看触发器" msgid "Create Trigger" msgstr "创建触发器" msgid "Edit Trigger" msgstr "编辑触发器" msgid "Delete Trigger" msgstr "删除触发器" msgid "Read execute record" msgstr "查看执行记录" msgid "ADMIN" msgstr "系统管理员" msgid "WORKSPACE_MANAGE" msgstr "空间管理员" msgid "USER" msgstr "普通用户" msgid "Generate share link" msgstr "生成分享链接" msgid "Chat record link" msgstr "聊天记录链接" msgid "Get chat record by share link" msgstr "通过分享链接获取聊天记录" msgid "Invalid chat record ids" msgstr "无效的聊天记录ID" msgid "Share link does not exist" msgstr "分享链接不存在" msgid "Chat has been deleted" msgstr "聊天记录已被删除" msgid "cron type requires cron_expression field" msgstr "cron 类型需要 cron_expression 字段" msgid "Invalid cron expression: %s" msgstr "Cron 表达式不合法:%s" msgid "Batch Remove Documents from Tag" msgstr "批量删除标签下的文档" msgid "Document does not belong to current knowledge" msgstr "文档不属于当前知识库" msgid "Move an application" msgstr "移动应用程序" ================================================ FILE: apps/locales/zh_Hant/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-06-18 17:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: apps/application/api/application_api.py:21 #: apps/application/serializers/application.py:153 msgid "Workflow Objects" msgstr "工作流對象" #: apps/application/api/application_api.py:52 #: apps/application/api/application_chat.py:104 #: apps/application/api/application_chat_record.py:74 #: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79 #: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110 #: apps/xpack/api/user_group.py:61 msgid "Current page" msgstr "當前頁" #: apps/application/api/application_api.py:59 #: apps/application/api/application_chat.py:111 #: apps/application/api/application_chat_record.py:81 #: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86 #: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117 #: apps/xpack/api/user_group.py:68 msgid "Page size" msgstr "每頁大小" #: apps/application/api/application_api.py:66 #: apps/application/serializers/application.py:156 #: apps/application/serializers/application.py:195 #: apps/application/serializers/application.py:274 #: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139 #: apps/knowledge/serializers/knowledge.py:52 #: apps/knowledge/serializers/knowledge.py:59 #: apps/tools/serializers/tool.py:453 #: apps/xpack/serializers/dataset_lark_serializer.py:55 msgid "folder id" msgstr "文件夾 ID" #: apps/application/api/application_api.py:73 #: apps/application/serializers/application.py:149 #: apps/application/serializers/application.py:275 #: apps/application/serializers/application.py:282 #: apps/application/serializers/application.py:368 #| msgid "Application" msgid "Application Name" msgstr "智能體名稱" #: apps/application/api/application_api.py:80 #: apps/application/serializers/application.py:152 #: apps/application/serializers/application.py:276 #: apps/application/serializers/application.py:283 #: apps/application/serializers/application.py:284 #: apps/application/serializers/application.py:370 #| msgid "Application/Version" msgid "Application Description" msgstr "智能體描述" #: apps/application/api/application_api.py:87 #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47 #: apps/application/serializers/application.py:99 #: apps/application/serializers/application.py:277 #: apps/application/serializers/application.py:295 #: apps/application/serializers/application.py:413 #: apps/application/serializers/application.py:540 #: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357 #: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52 #: apps/users/api/user.py:110 apps/users/api/user.py:126 #: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82 #: apps/workspace/serializers/workspace_serializers.py:270 #: apps/workspace/serializers/workspace_serializers.py:306 #: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92 #: apps/xpack/serializers/chat_user.py:301 msgid "User ID" msgstr "用戶 ID" #: apps/application/api/application_chat.py:70 #: apps/application/serializers/application_chat.py:56 msgid "Minimum number of likes" msgstr "最小點讚數" #: apps/application/api/application_chat.py:76 #: apps/application/serializers/application_chat.py:58 msgid "Minimum number of clicks" msgstr "最小點擊數" #: apps/application/api/application_chat.py:82 #: apps/application/flow/step_node/condition_node/i_condition_node.py:18 #: apps/application/serializers/application_chat.py:59 msgid "Comparator" msgstr "比較器" #: apps/application/api/application_chat_record.py:46 #: apps/application/api/application_chat_record.py:115 #: apps/application/serializers/application_chat.py:47 #: apps/application/serializers/application_chat_record.py:76 #| msgid "Chat" msgid "Chat ID" msgstr "對話 ID" #: apps/application/api/application_chat_record.py:53 #: apps/application/serializers/application_chat_record.py:77 msgid "Is it in order" msgstr "是否有序" #: apps/application/api/application_chat_record.py:122 msgid "Chat Record ID" msgstr "對話記錄 ID" #: apps/application/api/application_chat_record.py:129 #: apps/shared/api/shared_knowledge.py:235 #: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47 #: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111 #| msgid "Knowledge" msgid "Knowledge ID" msgstr "知識庫 ID" #: apps/application/api/application_chat_record.py:136 #: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86 #| msgid "Document" msgid "Document ID" msgstr "文檔 ID" #: apps/application/api/application_chat_record.py:148 #| msgid "Paragraph list" msgid "Paragraph ID" msgstr "段落 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26 #| msgid "type error" msgid "Model type error" msgstr "模型類型錯誤" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36 #: apps/common/field/common.py:24 apps/common/field/common.py:37 #| msgid "type error" msgid "Message type error" msgstr "消息類型錯誤" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55 #| msgid "Question list" msgid "Conversation list" msgstr "對話列表" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13 #: apps/application/flow/step_node/question_node/i_question_node.py:18 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13 #: apps/application/serializers/application.py:101 #: apps/application/serializers/application.py:285 #: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76 #: apps/shared/api/shared_model.py:98 msgid "Model id" msgstr "模型ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29 #| msgid "Paragraph list" msgid "Paragraph List" msgstr "段落列表" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_chat_record.py:41 #: apps/application/serializers/application_chat_record.py:140 #: apps/application/serializers/application_chat_record.py:179 #: apps/application/serializers/application_chat_record.py:247 #: apps/application/serializers/application_chat_record.py:312 #: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114 #| msgid "User relation ID" msgid "Conversation ID" msgstr "對話 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62 #: apps/application/flow/step_node/application_node/i_application_node.py:14 #: apps/application/serializers/application_chat.py:182 #: apps/chat/serializers/chat.py:40 #| msgid "Question list" msgid "User Questions" msgstr "用戶問題" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65 msgid "Post-processor" msgstr "後置處理器" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68 #| msgid "Create question" msgid "Completion Question" msgstr "完成問題" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70 msgid "Streaming Output" msgstr "流式輸出" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71 #: apps/xpack/serializers/resource_chat_user.py:93 #| msgid "user id" msgid "Chat user id" msgstr "對話用戶 ID" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73 #| msgid "Create user" msgid "Chat user Type" msgstr "對話用戶類型" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76 #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47 msgid "No reference segment settings" msgstr "無參考段設置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81 msgid "Model settings" msgstr "模型設置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28 #: apps/application/flow/step_node/question_node/i_question_node.py:29 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20 msgid "Model parameter settings" msgstr "模型參數設置" #: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91 msgid "message type error" msgstr "消息類型錯誤" #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224 #: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270 msgid "" "Sorry, the AI model is not configured. Please go to the application to set " "up the AI model first." msgstr "抱歉,AI 模型未配置,請先前往智能體設置 AI 模型。" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24 #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24 #: apps/application/serializers/application_chat_record.py:172 msgid "question" msgstr "問題" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32 #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27 msgid "History Questions" msgstr "歷史問題" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18 #: apps/application/flow/step_node/question_node/i_question_node.py:24 msgid "Number of multi-round conversations" msgstr "多輪對話的數量" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37 msgid "Maximum length of the knowledge base paragraph" msgstr "知識庫段落的最大長度" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39 #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16 #: apps/application/flow/step_node/question_node/i_question_node.py:21 #: apps/application/serializers/application.py:79 #: apps/application/serializers/application.py:124 #: apps/knowledge/serializers/common.py:72 msgid "Prompt word" msgstr "提示詞" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41 msgid "System prompt words (role)" msgstr "系統提示詞(角色)" #: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44 msgid "Completion problem" msgstr "完成問題" #: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32 #: apps/application/serializers/application.py:215 msgid "Question completion prompt" msgstr "問題完成提示" #: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20 #: apps/application/serializers/common.py:87 #, python-brace-format msgid "" "() contains the user's question. Answer the guessed user's question based on " "the context ({question}) Requirement: Output a complete question and put it " "in the tag" msgstr " () 包含用戶的問題。根據上下文({question})要求:輸出一個完整的問題並提出在標籤中" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27 msgid "System completes question text" msgstr "系統完成問題文本" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39 msgid "Dataset id list" msgstr "知識庫 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33 msgid "List of document ids to exclude" msgstr "排除的文檔 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36 msgid "List of exclusion vector ids" msgstr "排除的向量 ID 列表" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21 #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24 #: apps/application/serializers/application.py:84 #: apps/application/serializers/application_chat.py:185 msgid "Reference segment number" msgstr "引用分段數" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42 msgid "Similarity" msgstr "相似度" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30 #: apps/application/serializers/application.py:91 #: apps/knowledge/serializers/knowledge.py:100 #: apps/knowledge/serializers/knowledge.py:643 msgid "The type only supports embedding|keywords|blend" msgstr "類型僅支持嵌入|關鍵字|混合" #: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31 #: apps/application/serializers/application.py:92 msgid "Retrieval Mode" msgstr "檢索模式" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31 #: apps/application/serializers/application.py:113 #: apps/application/serializers/application.py:657 #: apps/application/serializers/application.py:664 #: apps/application/serializers/application.py:671 #: apps/knowledge/serializers/document.py:643 #: apps/knowledge/serializers/knowledge.py:220 #: apps/models_provider/serializers/model_serializer.py:116 #: apps/models_provider/serializers/model_serializer.py:134 #: apps/models_provider/serializers/model_serializer.py:370 #: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32 #: apps/shared/serializers/shared_model.py:65 #: apps/shared/serializers/shared_model.py:82 msgid "Model does not exist" msgstr "模型不存在" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33 msgid "No permission to use this model {model_name}" msgstr "無權限使用此模型{model_name}" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42 msgid "" "The vector model of the associated knowledge base is inconsistent and the " "segmentation cannot be recalled." msgstr "關聯的知識庫的向量模型不一致,無法回調分段。" #: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44 msgid "The knowledge base setting is wrong, please reset the knowledge base" msgstr "知識庫設置錯誤,請重置知識庫" #: apps/application/flow/common.py:206 #, python-brace-format msgid "The branch {branch} of the {node} node needs to be connected" msgstr "需要連接 {node} 節點的 {branch} 分支" #: apps/application/flow/common.py:212 #, python-brace-format msgid "{node} Nodes cannot be considered as end nodes" msgstr "{node} 節點不能被視為結束節點" #: apps/application/flow/common.py:226 msgid "The starting node is required" msgstr "開始節點是必需的" #: apps/application/flow/common.py:228 msgid "There can only be one starting node" msgstr "只能有一個開始節點" #: apps/application/flow/common.py:236 msgid "The node {node} model does not exist" msgstr "節點 {node} 模型不存在" #: apps/application/flow/common.py:246 #, python-brace-format msgid "Node {node} is unavailable" msgstr "節點 {node} 不可用" #: apps/application/flow/common.py:252 #, python-brace-format msgid "The library ID of node {node} cannot be empty" msgstr "工具庫 ID {node} 不能為空" #: apps/application/flow/common.py:255 #, python-brace-format msgid "The function library for node {node} is not available" msgstr "工具庫 {node} 不可用" #: apps/application/flow/common.py:261 msgid "Basic information node is required" msgstr "基本信息節點是必需的" #: apps/application/flow/common.py:263 msgid "There can only be one basic information node" msgstr "只能有一個基本信息節點" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15 #: apps/application/flow/step_node/question_node/i_question_node.py:20 #: apps/users/api/user.py:35 apps/users/api/user.py:102 msgid "Role Setting" msgstr "角色設置" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26 #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25 #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30 #: apps/application/flow/step_node/function_node/i_function_node.py:48 #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23 #: apps/application/flow/step_node/question_node/i_question_node.py:27 #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15 #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16 msgid "Whether to return content" msgstr "是否返回內容" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33 msgid "Context Type" msgstr "上下文類型" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35 msgid "Whether to enable MCP" msgstr "是否啟用 MCP" #: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36 msgid "MCP Server" msgstr "" #: apps/application/flow/step_node/application_node/i_application_node.py:12 #: apps/application/serializers/application.py:539 #: apps/application/serializers/application_access_token.py:44 #: apps/application/serializers/application_chat.py:38 #: apps/application/serializers/application_chat.py:54 #: apps/application/serializers/application_chat_record.py:43 #: apps/application/serializers/application_chat_record.py:75 #: apps/application/serializers/application_stats.py:35 #: apps/application/serializers/application_version.py:21 #: apps/application/serializers/application_version.py:67 #: apps/chat/serializers/chat.py:118 #: apps/chat/serializers/chat_authentication.py:80 #: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29 #: apps/xpack/api/platform.py:70 msgid "Application ID" msgstr "智能體 ID" #: apps/application/flow/step_node/application_node/i_application_node.py:15 msgid "API Input Fields" msgstr "API 輸入欄位" #: apps/application/flow/step_node/application_node/i_application_node.py:17 msgid "User Input Fields" msgstr "用戶輸入欄位" #: apps/application/flow/step_node/application_node/i_application_node.py:18 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25 #: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391 msgid "picture" msgstr "圖片" #: apps/application/flow/step_node/application_node/i_application_node.py:19 #: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12 #: apps/chat/serializers/chat.py:58 msgid "document" msgstr "文檔" #: apps/application/flow/step_node/application_node/i_application_node.py:20 #: apps/chat/serializers/chat.py:59 msgid "Audio" msgstr "音頻" #: apps/application/flow/step_node/application_node/i_application_node.py:22 #: apps/chat/serializers/chat.py:62 msgid "Child Nodes" msgstr "子節點" #: apps/application/flow/step_node/application_node/i_application_node.py:23 #: apps/application/flow/step_node/form_node/i_form_node.py:21 msgid "Form Data" msgstr "表單數據" #: apps/application/flow/step_node/application_node/i_application_node.py:57 msgid "" "Parameter value error: The uploaded document lacks file_id, and the document " "upload fails" msgstr "參數值錯誤:上傳的文檔缺少 file_id,文檔上傳失敗" #: apps/application/flow/step_node/application_node/i_application_node.py:66 msgid "" "Parameter value error: The uploaded image lacks file_id, and the image " "upload fails" msgstr "參數值錯誤:上傳的圖片缺少 file_id,圖片上傳失敗" #: apps/application/flow/step_node/application_node/i_application_node.py:76 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails." msgstr "參數值錯誤:上傳的音頻缺少 file_id,音頻上傳失敗" #: apps/application/flow/step_node/condition_node/i_condition_node.py:19 #: apps/models_provider/api/provide.py:24 msgid "value" msgstr "值" #: apps/application/flow/step_node/condition_node/i_condition_node.py:20 msgid "Fields" msgstr "欄位" #: apps/application/flow/step_node/condition_node/i_condition_node.py:24 msgid "Branch id" msgstr "分支 ID" #: apps/application/flow/step_node/condition_node/i_condition_node.py:25 msgid "Branch Type" msgstr "分支類型" #: apps/application/flow/step_node/condition_node/i_condition_node.py:26 msgid "Condition or|and" msgstr "條件 或|與" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20 msgid "Response Type" msgstr "響應類型" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21 #: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13 msgid "Reference Field" msgstr "引用欄位" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23 msgid "Direct answer content" msgstr "直接回答內容" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31 msgid "Reference field cannot be empty" msgstr "引用欄位不能為空" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33 msgid "Reference field error" msgstr "引用欄位錯誤" #: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36 msgid "Content cannot be empty" msgstr "內容不能為空" #: apps/application/flow/step_node/form_node/i_form_node.py:19 msgid "Form Configuration" msgstr "表單配置" #: apps/application/flow/step_node/form_node/i_form_node.py:20 msgid "Form output content" msgstr "表單輸出內容" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22 #: apps/application/flow/step_node/function_node/i_function_node.py:24 msgid "Variable Name" msgstr "變量名稱" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23 #: apps/application/flow/step_node/function_node/i_function_node.py:34 msgid "Variable Value" msgstr "變量名稱" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27 msgid "Library ID" msgstr "工具 ID" #: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36 msgid "The function has been deleted" msgstr "工具已被刪除" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43 msgid "Field: {name} No value set" msgstr "欄位:{name} 未設置值" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59 msgid "Field: {name} Type: {_type} Value: {value} Unsupported types" msgstr "欄位:{name} 類型:{_type} 值:{value} 類型轉換錯誤" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100 msgid "Field: {name} Type: {_type} Value: {value} Type error" msgstr "欄位:{name} 類型:{_type} 值:{value} 類型轉換錯誤" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91 #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96 #: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259 msgid "type error" msgstr "類型錯誤" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106 #: apps/tools/serializers/tool.py:398 msgid "Function does not exist" msgstr "工具不存在" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108 msgid "No permission to use this function {name}" msgstr "無權限使用此工具 {name}" #: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110 #, python-brace-format msgid "Function {name} is unavailable" msgstr "工具 {name} 不可用" #: apps/application/flow/step_node/function_node/i_function_node.py:25 msgid "Is this field required" msgstr "{keys} 是必填項" #: apps/application/flow/step_node/function_node/i_function_node.py:26 #: apps/knowledge/serializers/document.py:203 #: apps/tools/serializers/tool.py:120 msgid "type" msgstr "類型" #: apps/application/flow/step_node/function_node/i_function_node.py:28 msgid "The field only supports string|int|dict|array|float" msgstr "欄位僅支持字符串|整數|字典|數組|浮點數" #: apps/application/flow/step_node/function_node/i_function_node.py:30 #: apps/folders/serializers/folder.py:106 #: apps/folders/serializers/folder.py:141 #: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124 msgid "source" msgstr "來源" #: apps/application/flow/step_node/function_node/i_function_node.py:32 #: apps/tools/serializers/tool.py:126 msgid "The field only supports custom|reference" msgstr "欄位僅支持自定義|引用" #: apps/application/flow/step_node/function_node/i_function_node.py:40 msgid "{field}, this field is required." msgstr "{field_label} 欄位是必填項" #: apps/application/flow/step_node/function_node/i_function_node.py:46 msgid "function" msgstr "工具內容" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14 msgid "Prompt word (positive)" msgstr "提示詞 (正向)" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16 msgid "Prompt word (negative)" msgstr "提示詞 (負向)" #: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23 #: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20 msgid "Conversation storage type" msgstr "對話存儲類型" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13 msgid "Mcp servers" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16 msgid "Mcp server" msgstr "" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18 msgid "Mcp tool" msgstr "Mcp 工具" #: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21 msgid "Tool parameters" msgstr "工具參數" #: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26 #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33 msgid "Maximum number of words in a quoted segment" msgstr "引用段落的最大字數" #: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27 #: apps/knowledge/serializers/knowledge.py:97 #: apps/knowledge/serializers/knowledge.py:640 msgid "similarity" msgstr "相似度" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18 msgid "The audio file cannot be empty" msgstr "音頻文件不能為空" #: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33 msgid "" "Parameter value error: The uploaded audio lacks file_id, and the audio " "upload fails" msgstr "參數錯誤:上傳的音頻缺少 file_id,音頻上傳失敗" #: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18 msgid "Text content" msgstr "文本內容" #: apps/application/models/application_chat.py:79 #: apps/xpack/serializers/channel/chat_manage.py:94 #: apps/xpack/serializers/channel/chat_manage.py:152 msgid "" "Sorry, no relevant content was found. Please re-describe your problem or " "provide more information. " msgstr "不好意思,沒有找到相關內容。請重新描述您的問題或提供更多信息。" #: apps/application/serializers/application.py:78 msgid "No reference status" msgstr "無參考狀態" #: apps/application/serializers/application.py:86 msgid "Acquaintance" msgstr "相似度" #: apps/application/serializers/application.py:88 msgid "Maximum number of quoted characters" msgstr "引用字符的最大數量" #: apps/application/serializers/application.py:95 msgid "Segment settings not referenced" msgstr "引用段設置未引用" #: apps/application/serializers/application.py:104 #: apps/application/serializers/application_chat_record.py:176 #: apps/application/serializers/application_chat_record.py:252 #: apps/application/serializers/application_chat_record.py:317 msgid "Knowledge base id" msgstr "知識庫" #: apps/application/serializers/application.py:105 msgid "Knowledge Base List" msgstr "知識庫列表" #: apps/application/serializers/application.py:119 msgid "The knowledge base id does not exist" msgstr "知識庫 ID 不存在" #: apps/application/serializers/application.py:126 msgid "Role prompts" msgstr "角色提示" #: apps/application/serializers/application.py:128 msgid "No citation segmentation prompt" msgstr "無引用段落提示" #: apps/application/serializers/application.py:130 msgid "Thinking process switch" msgstr "思考過程開關" #: apps/application/serializers/application.py:134 msgid "The thinking process begins to mark" msgstr "思考過程開始標記" #: apps/application/serializers/application.py:138 msgid "End of thinking process marker" msgstr "思考過程結束標記" #: apps/application/serializers/application.py:155 #: apps/application/serializers/application.py:203 #: apps/application/serializers/application.py:378 msgid "Opening remarks" msgstr "開始提示" #: apps/application/serializers/application.py:191 msgid "application name" msgstr "智能體名稱" #: apps/application/serializers/application.py:194 msgid "application describe" msgstr "智能體描述" #: apps/application/serializers/application.py:197 #: apps/application/serializers/application.py:372 #: apps/common/constants/permission_constants.py:226 #: apps/common/constants/permission_constants.py:259 #: apps/common/constants/permission_constants.py:264 #: apps/models_provider/views/model.py:63 #: apps/models_provider/views/model.py:95 #: apps/models_provider/views/model.py:113 #: apps/models_provider/views/model.py:130 #: apps/models_provider/views/model.py:145 #: apps/models_provider/views/model.py:160 #: apps/models_provider/views/model.py:173 #: apps/models_provider/views/model.py:194 #: apps/models_provider/views/model.py:210 #: apps/models_provider/views/model_apply.py:29 #: apps/models_provider/views/model_apply.py:41 #: apps/models_provider/views/model_apply.py:53 #: apps/models_provider/views/provide.py:25 #: apps/models_provider/views/provide.py:48 #: apps/models_provider/views/provide.py:62 #: apps/models_provider/views/provide.py:80 #: apps/models_provider/views/provide.py:97 msgid "Model" msgstr "模型" #: apps/application/serializers/application.py:201 #: apps/application/serializers/application.py:376 msgid "Historical chat records" msgstr "歷史聊天記錄" #: apps/application/serializers/application.py:206 #: apps/application/serializers/application.py:380 msgid "Related Knowledge Base" msgstr "相關知識庫" #: apps/application/serializers/application.py:213 #: apps/application/serializers/application.py:390 msgid "Question completion" msgstr "問題完成" #: apps/application/serializers/application.py:217 msgid "Application Type" msgstr "智能體類型" #: apps/application/serializers/application.py:221 msgid "Application type only supports SIMPLE|WORK_FLOW" msgstr "智能體類型僅支持 SIMPLE|WORK_FLOW" #: apps/application/serializers/application.py:226 #: apps/application/serializers/application.py:394 msgid "Model parameters" msgstr "模型參數" #: apps/application/serializers/application.py:228 #: apps/application/serializers/application.py:396 msgid "Voice playback enabled" msgstr "開啟語音播放" #: apps/application/serializers/application.py:230 #: apps/application/serializers/application.py:398 msgid "Voice playback model ID" msgstr "語音播放模型 ID" #: apps/application/serializers/application.py:232 #: apps/application/serializers/application.py:400 msgid "Voice playback type" msgstr "語音播放類型" #: apps/application/serializers/application.py:234 #: apps/application/serializers/application.py:402 msgid "Voice playback autoplay" msgstr "自動播放語音" #: apps/application/serializers/application.py:236 #: apps/application/serializers/application.py:404 msgid "Voice recognition enabled" msgstr "開啟語音識別" #: apps/application/serializers/application.py:238 #: apps/application/serializers/application.py:406 msgid "Speech recognition model ID" msgstr "語音識別模型 ID" #: apps/application/serializers/application.py:240 #: apps/application/serializers/application.py:408 msgid "Voice recognition automatic transmission" msgstr "語音識別自動播放" #: apps/application/serializers/application.py:281 msgid "Primary key id" msgstr "" #: apps/application/serializers/application.py:286 msgid "Application type" msgstr "智能體類型" #: apps/application/serializers/application.py:287 #: apps/xpack/serializers/resource_chat_user.py:34 #: apps/xpack/serializers/resource_chat_user.py:110 #: apps/xpack/serializers/resource_chat_user_group.py:17 #: apps/xpack/serializers/resource_chat_user_group.py:85 msgid "Resource type" msgstr "資源類型" #: apps/application/serializers/application.py:288 msgid "Affiliation user" msgstr "關聯用戶" #: apps/application/serializers/application.py:289 msgid "Creation time" msgstr "創建時間" #: apps/application/serializers/application.py:290 msgid "Modification time" msgstr "修改時間" #: apps/application/serializers/application.py:294 #: apps/application/serializers/application_chat_record.py:42 #: apps/application/serializers/application_chat_record.py:323 #: apps/application/serializers/application_version.py:40 #: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147 #: apps/users/api/user.py:64 apps/users/api/user.py:170 #: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66 #: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103 #: apps/workspace/serializers/workspace_serializers.py:239 #: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40 #: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104 #: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63 msgid "Workspace ID" msgstr "工作空間 ID" #: apps/application/serializers/application.py:363 #: apps/knowledge/serializers/document.py:157 #: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57 #: apps/tools/serializers/tool.py:356 msgid "file" msgstr "文件" #: apps/application/serializers/application.py:384 msgid "Dataset settings" msgstr "知識庫設置" #: apps/application/serializers/application.py:387 msgid "Model setup" msgstr "模型設置" #: apps/application/serializers/application.py:391 msgid "Icon" msgstr "" #: apps/application/serializers/application.py:412 #: apps/application/serializers/application_api_key.py:33 #: apps/application/serializers/application_api_key.py:64 #: apps/folders/serializers/folder.py:101 #: apps/folders/serializers/folder.py:140 #: apps/folders/serializers/folder.py:195 #: apps/knowledge/serializers/document.py:253 #: apps/knowledge/serializers/document.py:347 #: apps/knowledge/serializers/document.py:408 #: apps/knowledge/serializers/document.py:502 #: apps/knowledge/serializers/document.py:736 #: apps/knowledge/serializers/document.py:888 #: apps/knowledge/serializers/document.py:963 #: apps/knowledge/serializers/document.py:983 #: apps/knowledge/serializers/document.py:1166 #: apps/knowledge/serializers/knowledge.py:208 #: apps/knowledge/serializers/knowledge.py:448 #: apps/knowledge/serializers/knowledge.py:557 #: apps/knowledge/serializers/knowledge.py:635 #: apps/knowledge/serializers/paragraph.py:134 #: apps/knowledge/serializers/paragraph.py:346 #: apps/knowledge/serializers/paragraph.py:438 #: apps/knowledge/serializers/paragraph.py:558 #: apps/knowledge/serializers/problem.py:176 #: apps/knowledge/serializers/problem.py:204 #: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86 #: apps/models_provider/api/model.py:99 #: apps/models_provider/serializers/model_serializer.py:259 #: apps/models_provider/serializers/model_serializer.py:323 #: apps/models_provider/serializers/model_serializer.py:392 #: apps/shared/api/shared_knowledge.py:131 #: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60 #: apps/shared/api/shared_tool.py:147 #: apps/shared/serializers/shared_knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:109 #: apps/shared/serializers/shared_knowledge.py:157 #: apps/shared/serializers/shared_model.py:110 #: apps/shared/serializers/shared_tool.py:45 #: apps/shared/serializers/shared_tool.py:86 #: apps/system_manage/serializers/user_resource_permission.py:74 #: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210 #: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358 #: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425 #: apps/tools/serializers/tool.py:452 #: apps/xpack/serializers/dataset_lark_serializer.py:45 #: apps/xpack/serializers/resource_chat_user.py:33 #: apps/xpack/serializers/resource_chat_user.py:109 #: apps/xpack/serializers/resource_chat_user_group.py:16 #: apps/xpack/serializers/resource_chat_user_group.py:84 msgid "workspace id" msgstr "工作空間ID" #: apps/application/serializers/application.py:459 msgid "" "The community version supports up to 5 applications. If you need more " "applications, please contact us (https://fit2cloud.com/)." msgstr "" "社區版支持最多5個智能體,如需更多智能體,請聯繫我們(https://fit2cloud.com/)。" #: apps/application/serializers/application.py:471 #: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56 #: apps/common/handle/impl/text/zip_split_handle.py:69 #: apps/knowledge/serializers/document.py:864 #: apps/knowledge/serializers/document.py:871 #: apps/tools/serializers/tool.py:370 msgid "Unsupported file format" msgstr "不支持的文件格式" #: apps/application/serializers/application.py:545 msgid "Application id does not exist" msgstr "智能體 ID 不存在" #: apps/application/serializers/application.py:591 msgid "work_flow is a required field" msgstr "工作流是必填欄位" #: apps/application/serializers/application.py:695 msgid "Unknown knowledge base id {dataset_id}, unable to associate" msgstr "未知知識庫 ID {dataset_id},無法關聯" #: apps/application/serializers/application_access_token.py:24 msgid "Reset Token" msgstr "重置令牌" #: apps/application/serializers/application_access_token.py:25 msgid "Is it enabled" msgstr "是否開啟" #: apps/application/serializers/application_access_token.py:28 msgid "Number of visits" msgstr "訪問次數" #: apps/application/serializers/application_access_token.py:30 msgid "Whether to enable whitelist" msgstr "是否啟用白名單" #: apps/application/serializers/application_access_token.py:32 #: apps/application/serializers/application_access_token.py:33 msgid "Whitelist" msgstr "白名單" #: apps/application/serializers/application_access_token.py:35 msgid "Whether to display knowledge sources" msgstr "是否展示知識來源" #: apps/application/serializers/application_access_token.py:37 #: apps/users/serializers/user.py:665 #: apps/xpack/serializers/application_setting_serializer.py:37 msgid "language" msgstr "語言" #: apps/application/serializers/application_api_key.py:21 msgid "Availability" msgstr "可用" #: apps/application/serializers/application_api_key.py:24 msgid "Is cross-domain allowed" msgstr "是否允許跨域" #: apps/application/serializers/application_api_key.py:28 msgid "Cross-domain address" msgstr "跨域地址" #: apps/application/serializers/application_api_key.py:29 msgid "Cross-domain list" msgstr "跨域列表" #: apps/application/serializers/application_api_key.py:34 #: apps/application/serializers/application_api_key.py:65 #: apps/knowledge/serializers/knowledge.py:72 #: apps/xpack/serializers/application_setting_serializer.py:77 #: apps/xpack/serializers/dataset_lark_serializer.py:295 msgid "application id" msgstr "智能體 ID" #: apps/application/serializers/application_api_key.py:41 #: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332 #: apps/xpack/serializers/application_setting_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:81 #: apps/xpack/serializers/platform_serializer.py:103 #: apps/xpack/serializers/platform_serializer.py:138 #: apps/xpack/serializers/platform_serializer.py:149 msgid "Application does not exist" msgstr "智能體不存在" #: apps/application/serializers/application_api_key.py:66 msgid "ApiKeyId" msgstr "ApiKey ID" #: apps/application/serializers/application_api_key.py:87 msgid "APIKey does not exist" msgstr "APIKey 不存在" #: apps/application/serializers/application_chat.py:33 msgid "chat id" msgstr "對話 ID" #: apps/application/serializers/application_chat.py:34 #: apps/application/serializers/application_chat.py:51 #: apps/application/serializers/application_chat.py:182 #: apps/application/serializers/application_version.py:23 msgid "summary" msgstr "摘要" #: apps/application/serializers/application_chat.py:35 msgid "Chat User ID" msgstr "對話用戶 ID" #: apps/application/serializers/application_chat.py:36 msgid "Chat User Type" msgstr "對話用戶類型" #: apps/application/serializers/application_chat.py:37 msgid "Is delete" msgstr "刪除" #: apps/application/serializers/application_chat.py:39 #: apps/application/serializers/application_stats.py:25 msgid "Number of conversations" msgstr "對話數量" #: apps/application/serializers/application_chat.py:40 #: apps/application/serializers/application_stats.py:29 msgid "Number of Likes" msgstr "點讚數量" #: apps/application/serializers/application_chat.py:41 #: apps/application/serializers/application_stats.py:31 msgid "Number of thumbs-downs" msgstr "點踩數量" #: apps/application/serializers/application_chat.py:42 msgid "Number of tags" msgstr "標籤數量" #: apps/application/serializers/application_chat.py:46 msgid "Chat ID List" msgstr "對話 ID 列表" #: apps/application/serializers/application_chat.py:52 #: apps/application/serializers/application_stats.py:36 #: apps/xpack/serializers/operate_log_serializer.py:55 msgid "Start time" msgstr "開始時間" #: apps/application/serializers/application_chat.py:53 #: apps/application/serializers/application_stats.py:37 #: apps/xpack/serializers/operate_log_serializer.py:56 msgid "End time" msgstr "結束時間" #: apps/application/serializers/application_chat.py:61 msgid "Only supports and|or" msgstr "只支持 與|或" #: apps/application/serializers/application_chat.py:183 msgid "Problem after optimization" msgstr "優化後的問題" #: apps/application/serializers/application_chat.py:184 msgid "answer" msgstr "回答" #: apps/application/serializers/application_chat.py:184 msgid "User feedback" msgstr "用戶反饋" #: apps/application/serializers/application_chat.py:186 msgid "Section title + content" msgstr "分段標題 + 內容" #: apps/application/serializers/application_chat.py:187 #: apps/application/views/application_chat_record.py:139 #: apps/application/views/application_chat_record.py:140 #: apps/application/views/application_chat_record.py:141 #: apps/common/constants/permission_constants.py:248 msgid "Annotation" msgstr "标注" #: apps/application/serializers/application_chat.py:187 msgid "Consuming tokens" msgstr "消耗的令牌" #: apps/application/serializers/application_chat.py:188 msgid "Time consumed (s)" msgstr "耗时 (s)" #: apps/application/serializers/application_chat.py:189 msgid "Question Time" msgstr "提問時間" #: apps/application/serializers/application_chat_record.py:44 #: apps/application/serializers/application_chat_record.py:143 #: apps/application/serializers/application_chat_record.py:250 #: apps/application/serializers/application_chat_record.py:315 #: apps/chat/serializers/chat.py:45 msgid "Conversation record id" msgstr "對話記錄 ID" #: apps/application/serializers/application_chat_record.py:51 msgid "Application authentication information does not exist" msgstr "智能體認證信息不存在" #: apps/application/serializers/application_chat_record.py:53 msgid "Displaying knowledge sources is not enabled" msgstr "知識庫來源展示未開啟" #: apps/application/serializers/application_chat_record.py:70 #: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274 msgid "Conversation does not exist" msgstr "對話不存在" #: apps/application/serializers/application_chat_record.py:152 #: apps/application/serializers/application_chat_record.py:279 #: apps/application/serializers/application_chat_record.py:336 #: apps/chat/serializers/chat.py:205 msgid "Conversation record does not exist" msgstr "對話記錄不存在" #: apps/application/serializers/application_chat_record.py:168 msgid "Section title" msgstr "章節標題" #: apps/application/serializers/application_chat_record.py:169 msgid "Paragraph content" msgstr "段落內容" #: apps/application/serializers/application_chat_record.py:177 #: apps/application/serializers/application_chat_record.py:254 #: apps/application/serializers/application_chat_record.py:319 msgid "Document id" msgstr "文檔 ID" #: apps/application/serializers/application_chat_record.py:184 #: apps/application/serializers/application_chat_record.py:260 #: apps/knowledge/serializers/paragraph.py:246 msgid "The document id is incorrect" msgstr "文檔 ID 不正確" #: apps/application/serializers/application_chat_record.py:203 msgid "Conversation records that do not exist" msgstr "對話記錄不存在" #: apps/application/serializers/application_chat_record.py:321 msgid "Paragraph id" msgstr "段落 ID" #: apps/application/serializers/application_chat_record.py:340 #, python-brace-format msgid "" "The paragraph id is wrong. The current conversation record does not exist. " "[{paragraph_id}] paragraph id" msgstr "段落 ID 錯誤。當前對話記錄不存在。[{paragraph_id}] 段落 ID" #: apps/application/serializers/application_stats.py:26 msgid "Number of new users" msgstr "新用戶數量" #: apps/application/serializers/application_stats.py:27 msgid "Total number of users" msgstr "總用戶數" #: apps/application/serializers/application_stats.py:28 msgid "date" msgstr "日期" #: apps/application/serializers/application_stats.py:30 msgid "Tokens consumption" msgstr "消耗的令牌" #: apps/application/serializers/application_version.py:36 msgid "Version Name" msgstr "版本名稱" #: apps/application/serializers/application_version.py:69 msgid "Workflow version id" msgstr "工作流版本 ID" #: apps/application/serializers/application_version.py:79 #: apps/application/serializers/application_version.py:94 msgid "Workflow version does not exist" msgstr "工作流版本不存在" #: apps/application/views/application.py:41 #: apps/application/views/application.py:42 #: apps/application/views/application.py:43 msgid "Create an application" msgstr "創建一個智能體程式" #: apps/application/views/application.py:47 #: apps/application/views/application.py:65 #: apps/application/views/application.py:82 #: apps/application/views/application.py:103 #: apps/application/views/application.py:123 #: apps/application/views/application.py:145 #: apps/application/views/application.py:166 #: apps/application/views/application.py:187 #: apps/application/views/application.py:206 #: apps/application/views/application_access_token.py:32 #: apps/application/views/application_access_token.py:47 #: apps/application/views/application_chat.py:102 #: apps/application/views/application_chat.py:122 #: apps/application/views/application_stats.py:33 #: apps/common/constants/permission_constants.py:224 #: apps/common/constants/permission_constants.py:234 #: apps/xpack/views/application_setting.py:29 #: apps/xpack/views/application_setting.py:47 msgid "Application" msgstr "智能體" #: apps/application/views/application.py:60 #: apps/application/views/application.py:61 #: apps/application/views/application.py:62 msgid "Get the application list" msgstr "獲取智能體列表" #: apps/application/views/application.py:77 #: apps/application/views/application.py:78 #: apps/application/views/application.py:79 msgid "Get the application list by page" msgstr "分頁獲取智能體列表" #: apps/application/views/application.py:97 #: apps/application/views/application.py:98 #: apps/application/views/application.py:99 msgid "Import Application" msgstr "導入智能體" #: apps/application/views/application.py:117 #: apps/application/views/application.py:118 #: apps/application/views/application.py:119 msgid "Export application" msgstr "導出智能體" #: apps/application/views/application.py:140 #: apps/application/views/application.py:141 #: apps/application/views/application.py:142 msgid "Deleting application" msgstr "刪除智能體" #: apps/application/views/application.py:160 #: apps/application/views/application.py:161 #: apps/application/views/application.py:162 msgid "Modify the application" msgstr "修改智能體" #: apps/application/views/application.py:181 #: apps/application/views/application.py:182 #: apps/application/views/application.py:183 msgid "Get application details" msgstr "獲取智能體詳情" #: apps/application/views/application.py:200 #: apps/application/views/application.py:201 #: apps/application/views/application.py:202 msgid "Publishing an application" msgstr "發布智能體" #: apps/application/views/application_access_token.py:27 #: apps/application/views/application_access_token.py:28 #: apps/application/views/application_access_token.py:29 msgid "Modify application access restriction information" msgstr "修改智能體訪問限制信息" #: apps/application/views/application_access_token.py:43 #: apps/application/views/application_access_token.py:44 #: apps/application/views/application_access_token.py:45 msgid "Get application access restriction information" msgstr "獲取智能體訪問限制信息" #: apps/application/views/application_api_key.py:31 #: apps/application/views/application_api_key.py:32 #: apps/application/views/application_api_key.py:33 msgid "Create application ApiKey" msgstr "創建智能體 API 密鑰" #: apps/application/views/application_api_key.py:37 #: apps/application/views/application_api_key.py:57 #: apps/application/views/application_api_key.py:77 #: apps/application/views/application_api_key.py:99 msgid "Application Api Key" msgstr "智能體 API 密鑰" #: apps/application/views/application_api_key.py:52 msgid "GET application ApiKey List" msgstr "獲取智能體的 API 密鑰列表" #: apps/application/views/application_api_key.py:53 #: apps/application/views/application_api_key.py:54 msgid "Create application ApiKey List" msgstr "創建智能體 API 密鑰列表" #: apps/application/views/application_api_key.py:71 #: apps/application/views/application_api_key.py:72 #: apps/application/views/application_api_key.py:73 msgid "Modify application API_KEY" msgstr "修改智能體 API 密鑰" #: apps/application/views/application_api_key.py:93 #: apps/application/views/application_api_key.py:94 #: apps/application/views/application_api_key.py:95 msgid "Delete Application API_KEY" msgstr "刪除智能體 API 密鑰" #: apps/application/views/application_chat.py:35 #: apps/application/views/application_chat.py:36 #: apps/application/views/application_chat.py:37 msgid "Get the conversation list" msgstr "獲取對話列表" #: apps/application/views/application_chat.py:41 #: apps/application/views/application_chat.py:61 #: apps/application/views/application_chat.py:82 #: apps/application/views/application_chat_record.py:37 #: apps/application/views/application_chat_record.py:58 #: apps/application/views/application_chat_record.py:82 #: apps/application/views/application_chat_record.py:106 #: apps/application/views/application_chat_record.py:125 #: apps/application/views/application_chat_record.py:145 #: apps/application/views/application_chat_record.py:171 msgid "Application/Conversation Log" msgstr "智能體/對話日誌" #: apps/application/views/application_chat.py:55 #: apps/application/views/application_chat.py:56 #: apps/application/views/application_chat.py:57 msgid "Get the conversation list by page" msgstr "分頁獲取對話列表" #: apps/application/views/application_chat.py:76 #: apps/application/views/application_chat.py:77 #: apps/application/views/application_chat.py:78 msgid "Export conversation" msgstr "導出對話" #: apps/application/views/application_chat.py:97 #: apps/application/views/application_chat.py:98 #: apps/application/views/application_chat.py:99 msgid "Get a temporary session id based on the application id" msgstr "獲取智能體的臨時會話 ID" #: apps/application/views/application_chat.py:116 #: apps/application/views/application_chat.py:117 #: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93 #: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95 msgid "dialogue" msgstr "對話" #: apps/application/views/application_chat_record.py:31 #: apps/application/views/application_chat_record.py:32 #: apps/application/views/application_chat_record.py:33 msgid "Get the conversation record list" msgstr "獲取對話記錄列表" #: apps/application/views/application_chat_record.py:52 #: apps/application/views/application_chat_record.py:53 #: apps/application/views/application_chat_record.py:54 msgid "Get the conversation record list by page" msgstr "分頁獲取對話記錄列表" #: apps/application/views/application_chat_record.py:76 #: apps/application/views/application_chat_record.py:77 #: apps/application/views/application_chat_record.py:78 msgid "Get conversation record details" msgstr "獲取對話記錄詳情" #: apps/application/views/application_chat_record.py:100 #: apps/application/views/application_chat_record.py:101 #: apps/application/views/application_chat_record.py:102 msgid "Add to Knowledge Base" msgstr "添加到知識庫" #: apps/application/views/application_chat_record.py:119 #: apps/application/views/application_chat_record.py:120 #: apps/application/views/application_chat_record.py:121 msgid "Get the list of marked paragraphs" msgstr "獲取標記段落列表" #: apps/application/views/application_chat_record.py:165 #: apps/application/views/application_chat_record.py:166 #: apps/application/views/application_chat_record.py:167 msgid "Delete a Annotation" msgstr "刪除注釋" #: apps/application/views/application_stats.py:28 #: apps/application/views/application_stats.py:29 #: apps/application/views/application_stats.py:30 msgid "Dialogue-related statistical trends" msgstr "與對話有關的統計趨勢" #: apps/application/views/application_version.py:30 #: apps/application/views/application_version.py:31 #: apps/application/views/application_version.py:32 msgid "Get the application version list" msgstr "獲取智能體版本列表" #: apps/application/views/application_version.py:35 #: apps/application/views/application_version.py:55 #: apps/application/views/application_version.py:76 #: apps/application/views/application_version.py:94 msgid "Application/Version" msgstr "智能體/ 版本" #: apps/application/views/application_version.py:50 #: apps/application/views/application_version.py:51 #: apps/application/views/application_version.py:52 msgid "Get the list of application versions by page" msgstr "分頁獲取智能體版本列表" #: apps/application/views/application_version.py:71 #: apps/application/views/application_version.py:72 #: apps/application/views/application_version.py:73 msgid "Get application version details" msgstr "獲取智能體版本詳情" #: apps/application/views/application_version.py:88 #: apps/application/views/application_version.py:89 #: apps/application/views/application_version.py:90 msgid "Modify application version information" msgstr "修改智能體版本信息" #: apps/chat/api/chat_authentication_api.py:38 #: apps/chat/serializers/chat_authentication.py:28 #: apps/chat/serializers/chat_authentication.py:54 #: apps/xpack/serializers/chat_auth.py:25 msgid "access_token" msgstr "" #: apps/chat/api/chat_embed_api.py:24 msgid "host" msgstr "" #: apps/chat/api/chat_embed_api.py:31 #: apps/chat/serializers/chat_embed_serializers.py:25 msgid "protocol" msgstr "協議" #: apps/chat/api/chat_embed_api.py:38 #: apps/chat/serializers/chat_embed_serializers.py:26 #: apps/users/serializers/login.py:36 msgid "token" msgstr "令牌" #: apps/chat/serializers/chat.py:42 msgid "Is the answer in streaming mode" msgstr "是否流式回答" #: apps/chat/serializers/chat.py:43 msgid "Do you want to reply again" msgstr "是否重新回復" #: apps/chat/serializers/chat.py:48 msgid "Node id" msgstr "節點 ID" #: apps/chat/serializers/chat.py:51 msgid "Runtime node id" msgstr "運行時節點 ID" #: apps/chat/serializers/chat.py:54 msgid "Node parameters" msgstr "節點參數" #: apps/chat/serializers/chat.py:56 msgid "Global variables" msgstr "全局變量" #: apps/chat/serializers/chat.py:60 #: apps/common/constants/permission_constants.py:222 #: apps/common/constants/permission_constants.py:228 msgid "Other" msgstr "其他" #: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320 msgid "Client id" msgstr "客戶端 ID" #: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321 msgid "Client Type" msgstr "客戶端類型" #: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322 #: apps/common/constants/permission_constants.py:240 msgid "Debug" msgstr "調試" #: apps/chat/serializers/chat.py:146 msgid "The number of visits exceeds today's visits" msgstr "今天的訪問次數超過限制" #: apps/chat/serializers/chat.py:157 msgid "The current model is not available" msgstr "當前模型不可用" #: apps/chat/serializers/chat.py:159 msgid "The model is downloading, please try again later" msgstr "下載過程被中斷,請重試" #: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357 msgid "The application has not been published. Please use it after publishing." msgstr "智能體未發布,請發布後使用。" #: apps/chat/serializers/chat_authentication.py:50 #: apps/xpack/serializers/chat_auth.py:53 msgid "Invalid access_token" msgstr "access_token 無效" #: apps/chat/serializers/chat_authentication.py:89 msgid "Illegal User" msgstr "非法用戶" #: apps/chat/serializers/chat_embed_serializers.py:24 msgid "Host" msgstr "" #: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38 #: apps/chat/views/chat.py:39 msgid "Application Anonymous Certification" msgstr "智能體匿名認證" #: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64 #: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99 #: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27 #: apps/xpack/views/chat_user_auth.py:419 msgid "Chat" msgstr "聊天" #: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60 #: apps/chat/views/chat.py:61 msgid "Get application related information" msgstr "獲取智能體相關信息" #: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77 #: apps/chat/views/chat.py:78 msgid "Get application authentication information" msgstr "獲取智能體認證信息" #: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116 #: apps/chat/views/chat.py:117 msgid "Get the session id according to the application id" msgstr "根據智能體 ID 獲取會話 ID" #: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132 #: apps/chat/views/chat.py:133 apps/users/views/login.py:70 #: apps/users/views/login.py:71 apps/users/views/login.py:72 msgid "Get captcha" msgstr "獲取驗證碼" #: apps/chat/views/chat.py:134 #: apps/common/constants/permission_constants.py:210 #: apps/users/views/login.py:41 apps/users/views/login.py:58 #: apps/users/views/login.py:73 apps/users/views/user.py:63 #: apps/users/views/user.py:77 apps/users/views/user.py:91 #: apps/users/views/user.py:108 apps/users/views/user.py:123 #: apps/users/views/user.py:136 apps/users/views/user.py:150 #: apps/users/views/user.py:164 apps/users/views/user.py:180 #: apps/users/views/user.py:193 apps/users/views/user.py:206 #: apps/users/views/user.py:217 apps/users/views/user.py:235 #: apps/users/views/user.py:251 apps/users/views/user.py:269 #: apps/users/views/user.py:286 apps/users/views/user.py:303 #: apps/users/views/user.py:321 apps/users/views/user.py:338 #: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206 msgid "User Management" msgstr "用戶管理" #: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23 #: apps/chat/views/chat_embed.py:24 msgid "Get embedded js" msgstr "獲取嵌入式 JavaScript" #: apps/common/auth/authenticate.py:80 msgid "Not logged in, please log in first" msgstr "未登錄,請先登錄" #: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89 #: apps/common/auth/authenticate.py:95 msgid "Authentication information is incorrect! illegal user" msgstr "身份驗證信息不正確!非法用戶" #: apps/common/auth/authentication.py:98 msgid "No permission to access" msgstr "無權限訪問" #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43 #: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49 #: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43 #: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49 msgid "Authentication information is incorrect" msgstr "身份驗證信息不正確" #: apps/common/auth/handle/impl/user_token.py:265 msgid "Login expired" msgstr "登錄已過期" #: apps/common/constants/exception_code_constants.py:31 #: apps/users/serializers/login.py:53 #: apps/xpack/serializers/chat_user_serializer.py:123 msgid "The username or password is incorrect" msgstr "用戶名或密碼不正確" #: apps/common/constants/exception_code_constants.py:32 msgid "Please log in first and bring the user Token" msgstr "請先登錄並攜帶用戶 Token" #: apps/common/constants/exception_code_constants.py:33 #: apps/users/serializers/user.py:630 msgid "Email sending failed" msgstr "郵件發送失敗" #: apps/common/constants/exception_code_constants.py:34 msgid "Email format error" msgstr "郵箱格式錯誤" #: apps/common/constants/exception_code_constants.py:35 msgid "The email has been registered, please log in directly" msgstr "該郵箱已註冊,請直接登錄" #: apps/common/constants/exception_code_constants.py:36 msgid "The email is not registered, please register first" msgstr "該郵箱未註冊,請先註冊" #: apps/common/constants/exception_code_constants.py:38 msgid "The verification code is incorrect or the verification code has expired" msgstr "驗證碼不正確或已過期" #: apps/common/constants/exception_code_constants.py:39 msgid "The username has been registered, please log in directly" msgstr "用戶名已註冊,請直接登錄" #: apps/common/constants/exception_code_constants.py:41 msgid "" "The username cannot be empty and must be between 6 and 20 characters long." msgstr "用戶名不能為空,且長度在6到20個字符之間。" #: apps/common/constants/exception_code_constants.py:43 msgid "Password and confirmation password are inconsistent" msgstr "密碼和確認密碼不一致" #: apps/common/constants/exception_code_constants.py:44 msgid "The nickname is already registered" msgstr "暱稱已註冊" #: apps/common/constants/permission_constants.py:209 msgid "System Setting" msgstr "系統設置" #: apps/common/constants/permission_constants.py:211 #: apps/common/constants/permission_constants.py:272 #: apps/role_setting/views/role_setting.py:44 #: apps/role_setting/views/role_setting.py:67 #: apps/role_setting/views/role_setting.py:84 #: apps/role_setting/views/role_setting.py:103 #: apps/role_setting/views/role_setting.py:125 #: apps/role_setting/views/role_setting.py:145 #: apps/role_setting/views/role_setting.py:167 #: apps/role_setting/views/role_setting.py:191 #: apps/role_setting/views/role_setting.py:210 msgid "Role" msgstr "角色" #: apps/common/constants/permission_constants.py:212 #: apps/common/constants/permission_constants.py:270 #: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49 #: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80 #: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119 #: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155 #: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188 #: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223 #: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250 msgid "Workspace" msgstr "工作空間" #: apps/common/constants/permission_constants.py:213 msgid "Resource Application" msgstr "資源管理-智能體" #: apps/common/constants/permission_constants.py:214 msgid "Resource Knowledge" msgstr "資源管理-知識庫" #: apps/common/constants/permission_constants.py:215 msgid "Resource Tool" msgstr "資源管理-工具" #: apps/common/constants/permission_constants.py:216 msgid "Resource Model" msgstr "資源管理-模型" #: apps/common/constants/permission_constants.py:217 msgid "Resource Permission" msgstr "資源授權" #: apps/common/constants/permission_constants.py:218 #: apps/shared/views/shared_dataset_lark_views.py:30 #: apps/shared/views/shared_dataset_lark_views.py:50 #: apps/shared/views/shared_knowledge.py:33 #: apps/shared/views/shared_knowledge.py:53 #: apps/shared/views/shared_knowledge.py:76 #: apps/shared/views/shared_knowledge.py:91 #: apps/shared/views/shared_knowledge.py:106 #: apps/shared/views/shared_knowledge.py:125 #: apps/shared/views/shared_knowledge.py:151 #: apps/shared/views/shared_knowledge.py:178 #: apps/shared/views/shared_knowledge.py:196 #: apps/shared/views/shared_knowledge.py:214 #: apps/shared/views/shared_knowledge.py:235 #: apps/shared/views/shared_knowledge.py:256 #: apps/shared/views/shared_knowledge.py:276 #: apps/shared/views/shared_knowledge.py:297 #: apps/shared/views/shared_knowledge.py:312 #: apps/shared/views/shared_knowledge.py:331 #: apps/shared/views/shared_knowledge.py:354 #: apps/shared/views/shared_knowledge.py:386 #: apps/shared/views/shared_knowledge.py:407 msgid "Shared Knowledge" msgstr "共享資源-知識庫" #: apps/common/constants/permission_constants.py:219 #: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58 #: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107 #: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138 #: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166 #: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202 #: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234 #: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270 msgid "Shared Model" msgstr "共享資源-模型" #: apps/common/constants/permission_constants.py:220 #: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49 #: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83 #: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116 #: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157 #: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194 #: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239 #: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273 #: apps/shared/views/shared_tool.py:294 msgid "Shared Tool" msgstr "共享資源-工具" #: apps/common/constants/permission_constants.py:221 msgid "Operation Log" msgstr "操作日誌" #: apps/common/constants/permission_constants.py:223 msgid "System Management" msgstr "系統管理" #: apps/common/constants/permission_constants.py:225 #: apps/common/constants/permission_constants.py:235 #: apps/common/constants/permission_constants.py:260 #: apps/common/constants/permission_constants.py:265 msgid "Knowledge" msgstr "知識庫" #: apps/common/constants/permission_constants.py:227 #: apps/common/constants/permission_constants.py:258 #: apps/common/constants/permission_constants.py:263 #: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61 #: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104 #: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146 #: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201 #: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250 #: apps/tools/views/tool.py:274 msgid "Tool" msgstr "工具" #: apps/common/constants/permission_constants.py:229 msgid "Read" msgstr "查看" #: apps/common/constants/permission_constants.py:230 msgid "Edit" msgstr "編輯" #: apps/common/constants/permission_constants.py:231 msgid "Create" msgstr "創建" #: apps/common/constants/permission_constants.py:232 msgid "Delete" msgstr "刪除" #: apps/common/constants/permission_constants.py:233 msgid "Email Setting" msgstr "郵箱設置" #: apps/common/constants/permission_constants.py:236 #: apps/common/constants/permission_constants.py:261 #: apps/common/constants/permission_constants.py:266 msgid "Document" msgstr "文檔" #: apps/common/constants/permission_constants.py:237 #: apps/common/constants/permission_constants.py:262 #: apps/common/constants/permission_constants.py:267 msgid "Problem" msgstr "問題" #: apps/common/constants/permission_constants.py:238 msgid "Import" msgstr "導入" #: apps/common/constants/permission_constants.py:239 msgid "Export" msgstr "導出" #: apps/common/constants/permission_constants.py:241 msgid "Sync" msgstr "同步" #: apps/common/constants/permission_constants.py:242 msgid "Generate" msgstr "生成問題" #: apps/common/constants/permission_constants.py:243 msgid "Add Member" msgstr "添加成員" #: apps/common/constants/permission_constants.py:244 msgid "Remove Member" msgstr "移除成員" #: apps/common/constants/permission_constants.py:245 msgid "Vector" msgstr "向量化" #: apps/common/constants/permission_constants.py:246 msgid "Migrate" msgstr "遷移" #: apps/common/constants/permission_constants.py:247 msgid "Relate" msgstr "關聯分段" #: apps/common/constants/permission_constants.py:249 msgid "Clear Policy" msgstr "清除策略" #: apps/common/constants/permission_constants.py:250 msgid "Login Auth" msgstr "登錄認證" #: apps/common/constants/permission_constants.py:251 msgid "Display Settings" msgstr "顯示設置" #: apps/common/constants/permission_constants.py:252 #: apps/common/constants/permission_constants.py:720 #: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38 #: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71 msgid "System API Key" msgstr "系統 API Key" #: apps/common/constants/permission_constants.py:253 #: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42 msgid "Appearance Settings" msgstr "外觀設置" #: apps/common/constants/permission_constants.py:254 #: apps/common/constants/permission_constants.py:269 #: apps/xpack/views/system_chat_user.py:339 #: apps/xpack/views/system_chat_user.py:362 msgid "Chat User" msgstr "對話用戶" #: apps/common/constants/permission_constants.py:255 #: apps/common/constants/permission_constants.py:268 msgid "User Group" msgstr "用戶組" #: apps/common/constants/permission_constants.py:256 msgid "Chat User Auth" msgstr "對話用戶認證" #: apps/common/constants/permission_constants.py:257 msgid "Overview" msgstr "概覽" #: apps/common/constants/permission_constants.py:271 #: apps/common/constants/permission_constants.py:671 #: apps/common/constants/permission_constants.py:677 #: apps/common/constants/permission_constants.py:683 #: apps/common/constants/permission_constants.py:689 msgid "Dialogue log" msgstr "對話日誌" #: apps/common/constants/permission_constants.py:641 msgid "Embed third party" msgstr "嵌入第三方" #: apps/common/constants/permission_constants.py:647 msgid "Access restrictions" msgstr "訪問限制" #: apps/common/constants/permission_constants.py:653 msgid "Display settings" msgstr "顯示設置" #: apps/common/constants/permission_constants.py:659 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75 msgid "API Key" msgstr "" #: apps/common/constants/permission_constants.py:665 msgid "Public settings" msgstr "公共訪問連接" #: apps/common/constants/permission_constants.py:704 msgid "About" msgstr "關於" #: apps/common/constants/permission_constants.py:709 #: apps/users/views/user.py:88 apps/users/views/user.py:89 #: apps/users/views/user.py:90 msgid "Switch Language" msgstr "切換語言" #: apps/common/constants/permission_constants.py:714 msgid "Change Password" msgstr "修改密碼" #: apps/common/constants/permission_constants.py:734 msgid "Sync users" msgstr "同步用戶" #: apps/common/constants/permission_constants.py:755 #: apps/common/constants/permission_constants.py:808 msgid "Set up user groups" msgstr "設置用戶組" #: apps/common/event/__init__.py:27 msgid "The download process was interrupted, please try again" msgstr "下載過程被中斷,請重試" #: apps/common/event/listener_manage.py:90 #, python-brace-format msgid "Query vector data: {paragraph_id_list} error {error} {traceback}" msgstr "查詢向量數據:{paragraph_id_list} 錯誤:{error} {traceback}" #: apps/common/event/listener_manage.py:95 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id_list}" msgstr "開始--->向量段落: {paragraph_id_list}" #: apps/common/event/listener_manage.py:107 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id_list} error {error} {traceback}" msgstr "向量段落: {paragraph_id_list} 錯誤:{error} {traceback}" #: apps/common/event/listener_manage.py:113 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id_list}" msgstr "結束--->向量段落: {paragraph_id_list}" #: apps/common/event/listener_manage.py:122 #, python-brace-format msgid "Start--->Embedding paragraph: {paragraph_id}" msgstr "開始--->向量段落: {paragraph_id}" #: apps/common/event/listener_manage.py:147 #, python-brace-format msgid "Vectorized paragraph: {paragraph_id} error {error} {traceback}" msgstr "向量段落: {paragraph_id} 錯誤:{error} {traceback}" #: apps/common/event/listener_manage.py:152 #, python-brace-format msgid "End--->Embedding paragraph: {paragraph_id}" msgstr "結束--->向量段落: {paragraph_id}" #: apps/common/event/listener_manage.py:268 #, python-brace-format msgid "Start--->Embedding document: {document_id}" msgstr "開始--->向量文檔: {document_id}" #: apps/common/event/listener_manage.py:288 #, python-brace-format msgid "Vectorized document: {document_id} error {error} {traceback}" msgstr "向量文檔: {document_id} 錯誤:{error} {traceback}" #: apps/common/event/listener_manage.py:293 #, python-brace-format msgid "End--->Embedding document: {document_id}" msgstr "結束--->向量文檔: {document_id}" #: apps/common/event/listener_manage.py:304 #, python-brace-format msgid "Start--->Embedding knowledge: {knowledge_id}" msgstr "開始--->向量知識庫: {knowledge_id}" #: apps/common/event/listener_manage.py:308 #, python-brace-format msgid "Start--->Embedding document: {document_list}" msgstr "開始--->向量文檔: {document_list}" #: apps/common/event/listener_manage.py:312 #: apps/knowledge/task/embedding.py:116 #, python-brace-format msgid "Vectorized knowledge: {knowledge_id} error {error} {traceback}" msgstr "向量知識庫: {knowledge_id} 錯誤:{error} {traceback}" #: apps/common/event/listener_manage.py:315 #, python-brace-format msgid "End--->Embedding knowledge: {knowledge_id}" msgstr "結束--->向量知識庫: {knowledge_id}" #: apps/common/exception/handle_exception.py:32 #: apps/common/handle/handle_exception.py:33 msgid "Unknown exception" msgstr "未知錯誤" #: apps/common/field/common.py:48 msgid "not a function" msgstr "不是函數" #: apps/common/forms/base_field.py:64 #, python-brace-format msgid "The field {field_label} is required" msgstr "{field_label} 欄位是必填項" #: apps/common/forms/slider_field.py:56 #, python-brace-format msgid "The {field_label} cannot be less than {min}" msgstr "{field_label} 不能小於{min}" #: apps/common/forms/slider_field.py:62 #, python-brace-format msgid "The {field_label} cannot be greater than {max}" msgstr "{field_label} 不能大於{max}" #: apps/common/handle/impl/text/pdf_split_handle.py:281 #, python-brace-format msgid "This document has no preface and is treated as ordinary text: {e}" msgstr "該文檔沒有前言,視為普通文本: {e}" #: apps/common/job/clean_chat_job.py:23 msgid "start clean chat log" msgstr "開始清理聊天日誌" #: apps/common/job/clean_chat_job.py:69 msgid "end clean chat log" msgstr "結束清理聊天日誌" #: apps/common/job/clean_debug_file_job.py:21 msgid "start clean debug file" msgstr "開始清理調試文件" #: apps/common/job/clean_debug_file_job.py:25 msgid "end clean debug file" msgstr "結束清理調試文件" #: apps/common/result/api.py:17 apps/common/result/api.py:27 msgid "response code" msgstr "響應碼" #: apps/common/result/api.py:18 apps/common/result/api.py:19 #: apps/common/result/api.py:28 apps/common/result/api.py:29 msgid "error prompt" msgstr "錯誤提示" #: apps/common/result/api.py:43 msgid "total number of data" msgstr "總數據" #: apps/common/result/api.py:44 msgid "current page" msgstr "當前頁" #: apps/common/result/api.py:45 msgid "page size" msgstr "每頁大小" #: apps/common/result/result.py:31 #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Success" msgstr "成功" #: apps/common/utils/common.py:91 msgid "Text-to-speech node, the text content must be of string type" msgstr "文本轉語音節點,文本內容必須是字符串類型" #: apps/common/utils/common.py:93 msgid "Text-to-speech node, the text content cannot be empty" msgstr "文本轉語音節點,文本內容不能為空" #: apps/common/utils/common.py:246 #, python-brace-format msgid "Limit {count} exceeded, please contact us (https://fit2cloud.com/)." msgstr "超過限制 {count},請聯繫我們 (https://fit2cloud.com/)." #: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17 #: apps/folders/serializers/folder.py:98 msgid "folder name" msgstr "文件夾名稱" #: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19 #: apps/folders/serializers/folder.py:99 msgid "folder description" msgstr "文件夾描述" #: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23 #: apps/folders/serializers/folder.py:102 msgid "parent id" msgstr "父級 ID" #: apps/folders/serializers/folder.py:75 msgid "Folder depth cannot exceed 5 levels" msgstr "文件夾深度不能超過5級" #: apps/folders/serializers/folder.py:100 msgid "folder user id" msgstr "文件夾用戶 ID" #: apps/folders/serializers/folder.py:105 #: apps/knowledge/serializers/knowledge.py:112 #: apps/knowledge/serializers/knowledge.py:207 #: apps/knowledge/serializers/knowledge.py:447 #: apps/knowledge/serializers/knowledge.py:559 #: apps/knowledge/serializers/knowledge.py:637 #: apps/models_provider/serializers/model_serializer.py:108 #: apps/models_provider/serializers/model_serializer.py:212 #: apps/models_provider/serializers/model_serializer.py:252 #: apps/shared/serializers/shared_knowledge.py:107 #: apps/shared/serializers/shared_knowledge.py:156 #: apps/shared/serializers/shared_tool.py:84 #: apps/system_manage/serializers/user_resource_permission.py:75 #: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209 #: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664 #: apps/xpack/serializers/dataset_lark_serializer.py:46 #: apps/xpack/serializers/dataset_lark_serializer.py:285 #: apps/xpack/serializers/system_api_key.py:23 msgid "user id" msgstr "用戶ID" #: apps/folders/serializers/folder.py:123 msgid "Folder name already exists" msgstr "文件夾名稱已存在" #: apps/folders/serializers/folder.py:150 #: apps/folders/serializers/folder.py:182 msgid "Folder does not exist" msgstr "文件夾不存在" #: apps/folders/serializers/folder.py:184 msgid "Cannot delete root folder" msgstr "無法刪除根文件夾" #: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32 #: apps/folders/views/folder.py:33 msgid "Create folder" msgstr "創建文件夾" #: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63 #: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110 #: apps/folders/views/folder.py:129 msgid "Folder" msgstr "文件夾" #: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59 #: apps/folders/views/folder.py:60 msgid "Get folder tree" msgstr "獲取文件夾樹" #: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81 #: apps/folders/views/folder.py:82 msgid "Update folder" msgstr "更新文件夾" #: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106 #: apps/folders/views/folder.py:107 msgid "Get folder" msgstr "獲取文件夾" #: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125 #: apps/folders/views/folder.py:126 msgid "Delete folder" msgstr "刪除文件夾" #: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52 #: apps/knowledge/serializers/problem.py:40 msgid "problem list" msgstr "問題列表" #: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53 #: apps/knowledge/serializers/problem.py:41 msgid "problem" msgstr "問題 ID" #: apps/knowledge/serializers/common.py:32 #: apps/knowledge/serializers/knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:29 msgid "source url" msgstr "來源" #: apps/knowledge/serializers/common.py:33 #: apps/knowledge/serializers/document.py:152 msgid "selector" msgstr "選擇器" #: apps/knowledge/serializers/common.py:40 #, python-brace-format msgid "URL error, cannot parse [{source_url}]" msgstr "URL 錯誤,無法解析 [{source_url}]" #: apps/knowledge/serializers/common.py:48 #: apps/knowledge/serializers/document.py:78 #: apps/knowledge/serializers/document.py:170 #: apps/knowledge/serializers/document.py:186 msgid "id list" msgstr "ID 列表" #: apps/knowledge/serializers/common.py:58 #, python-brace-format msgid "The following id does not exist: {error_id_list}" msgstr "以下ID不存在: {error_id_list}" #: apps/knowledge/serializers/common.py:74 #: apps/knowledge/serializers/document.py:166 #: apps/knowledge/serializers/document.py:171 #: apps/knowledge/serializers/document.py:178 msgid "state list" msgstr "狀態列表" #: apps/knowledge/serializers/common.py:117 #: apps/knowledge/serializers/common.py:141 msgid "The knowledge base is inconsistent with the vector model" msgstr "知識庫與向量模型不一致" #: apps/knowledge/serializers/common.py:119 #: apps/knowledge/serializers/common.py:143 msgid "Knowledge base setting error, please reset the knowledge base" msgstr "知識庫設置錯誤,請重置知識庫" #: apps/knowledge/serializers/document.py:79 #: apps/knowledge/serializers/document.py:97 #: apps/knowledge/serializers/document.py:353 msgid "task type" msgstr "任務類型" #: apps/knowledge/serializers/document.py:87 #: apps/knowledge/serializers/document.py:105 msgid "task type not support" msgstr "任務類型不支持" #: apps/knowledge/serializers/document.py:91 #: apps/knowledge/serializers/document.py:110 #: apps/knowledge/serializers/document.py:350 msgid "document name" msgstr "文檔名稱" #: apps/knowledge/serializers/document.py:93 msgid "source file id" msgstr "源文件 ID" #: apps/knowledge/serializers/document.py:113 #: apps/knowledge/serializers/document.py:194 msgid "The type only supports optimization|directly_return" msgstr "該類型僅支持優化|直接返回" #: apps/knowledge/serializers/document.py:115 #: apps/knowledge/serializers/document.py:187 #: apps/knowledge/serializers/document.py:351 msgid "hit handling method" msgstr "命中處理方法" #: apps/knowledge/serializers/document.py:118 #: apps/knowledge/serializers/document.py:189 msgid "directly return similarity" msgstr "直接返回相似度" #: apps/knowledge/serializers/document.py:120 #: apps/knowledge/serializers/document.py:352 msgid "document is active" msgstr "文檔已激活" #: apps/knowledge/serializers/document.py:139 #: apps/knowledge/serializers/document.py:156 #: apps/knowledge/serializers/document.py:161 msgid "file list" msgstr "文件 列表" #: apps/knowledge/serializers/document.py:140 msgid "limit" msgstr "限制" #: apps/knowledge/serializers/document.py:143 #: apps/knowledge/serializers/document.py:144 msgid "patterns" msgstr "分割符" #: apps/knowledge/serializers/document.py:146 msgid "Auto Clean" msgstr "自動清理" #: apps/knowledge/serializers/document.py:150 #: apps/knowledge/serializers/document.py:151 msgid "document url list" msgstr "文檔 URL 列表" #: apps/knowledge/serializers/document.py:175 #: apps/knowledge/serializers/document.py:182 msgid "document id list" msgstr "文檔 ID 列表" #: apps/knowledge/serializers/document.py:176 #: apps/knowledge/serializers/paragraph.py:58 #: apps/models_provider/api/model.py:105 #: apps/models_provider/serializers/model_apply_serializers.py:51 #: apps/models_provider/serializers/model_serializer.py:107 #: apps/models_provider/serializers/model_serializer.py:364 #: apps/shared/api/shared_model.py:61 #: apps/shared/serializers/shared_model.py:54 msgid "model id" msgstr "模型ID" #: apps/knowledge/serializers/document.py:177 #: apps/knowledge/serializers/paragraph.py:59 msgid "prompt" msgstr "提示詞" #: apps/knowledge/serializers/document.py:201 msgid "The template type only supports excel|csv" msgstr "模板類型僅支持 excel|csv" #: apps/knowledge/serializers/document.py:254 #: apps/knowledge/serializers/document.py:348 #: apps/knowledge/serializers/document.py:409 #: apps/knowledge/serializers/document.py:504 #: apps/knowledge/serializers/document.py:889 #: apps/knowledge/serializers/document.py:964 #: apps/knowledge/serializers/document.py:984 #: apps/knowledge/serializers/document.py:1167 #: apps/knowledge/serializers/knowledge.py:209 #: apps/knowledge/serializers/knowledge.py:558 #: apps/knowledge/serializers/paragraph.py:70 #: apps/knowledge/serializers/paragraph.py:138 #: apps/knowledge/serializers/paragraph.py:239 #: apps/knowledge/serializers/paragraph.py:321 #: apps/knowledge/serializers/paragraph.py:347 #: apps/knowledge/serializers/paragraph.py:398 #: apps/knowledge/serializers/paragraph.py:439 #: apps/knowledge/serializers/paragraph.py:559 #: apps/knowledge/serializers/problem.py:62 #: apps/knowledge/serializers/problem.py:126 #: apps/knowledge/serializers/problem.py:177 #: apps/knowledge/serializers/problem.py:205 #: apps/shared/api/shared_knowledge.py:196 #: apps/shared/api/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:158 #: apps/shared/serializers/shared_knowledge.py:205 #: apps/xpack/serializers/dataset_lark_serializer.py:104 #: apps/xpack/serializers/dataset_lark_serializer.py:263 #: apps/xpack/serializers/dataset_lark_serializer.py:284 msgid "knowledge id" msgstr "知識庫 ID" #: apps/knowledge/serializers/document.py:255 #: apps/knowledge/serializers/paragraph.py:441 msgid "target knowledge id" msgstr "當前知識庫 ID" #: apps/knowledge/serializers/document.py:256 msgid "document list" msgstr "文檔列表" #: apps/knowledge/serializers/document.py:257 #: apps/knowledge/serializers/document.py:410 #: apps/knowledge/serializers/document.py:503 #: apps/knowledge/serializers/document.py:737 #: apps/knowledge/serializers/paragraph.py:61 #: apps/knowledge/serializers/paragraph.py:71 #: apps/knowledge/serializers/paragraph.py:140 #: apps/knowledge/serializers/paragraph.py:240 #: apps/knowledge/serializers/paragraph.py:322 #: apps/knowledge/serializers/paragraph.py:349 #: apps/knowledge/serializers/paragraph.py:399 #: apps/knowledge/serializers/paragraph.py:440 #: apps/knowledge/serializers/paragraph.py:560 #: apps/knowledge/serializers/problem.py:36 #: apps/knowledge/serializers/problem.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:160 msgid "document id" msgstr "文檔 ID" #: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25 #: apps/xpack/serializers/operate_log_serializer.py:60 #: apps/xpack/serializers/operate_log_serializer.py:174 msgid "status" msgstr "狀態" #: apps/knowledge/serializers/document.py:355 msgid "order by" msgstr "排序" #: apps/knowledge/serializers/document.py:417 #: apps/knowledge/serializers/document.py:510 #: apps/xpack/serializers/dataset_lark_serializer.py:167 #: apps/xpack/serializers/dataset_lark_serializer.py:189 msgid "document id not exist" msgstr "文檔 ID 不存在" #: apps/knowledge/serializers/document.py:419 #: apps/knowledge/serializers/knowledge.py:570 msgid "Synchronization is only supported for web site types" msgstr "僅支持網站類型的同步" #: apps/knowledge/serializers/document.py:661 msgid "The task is being executed, please do not send it repeatedly." msgstr "任務正在執行,請勿重複發送。" #: apps/knowledge/serializers/document.py:674 msgid "Section title (optional)" msgstr "章節標題" #: apps/knowledge/serializers/document.py:675 msgid "" "Section content (required, question answer, no more than 4096 characters)" msgstr "章節內容(必填,問答,不超過4096個字符)" #: apps/knowledge/serializers/document.py:676 msgid "Question (optional, one per line in the cell)" msgstr "問題(可選,每個單元格一行)" #: apps/knowledge/serializers/document.py:742 msgid "knowledge id not exist" msgstr "知識庫 ID 不存在" #: apps/knowledge/serializers/document.py:898 msgid "The maximum size of the uploaded file cannot exceed {}MB" msgstr "上傳文件的最大大小不能超過 {}MB" #: apps/knowledge/serializers/document.py:976 msgid "space" msgstr "空格" #: apps/knowledge/serializers/document.py:977 msgid "semicolon" msgstr "分號" #: apps/knowledge/serializers/document.py:977 msgid "comma" msgstr "逗號" #: apps/knowledge/serializers/document.py:978 msgid "period" msgstr "句號" #: apps/knowledge/serializers/document.py:978 msgid "enter" msgstr "回車" #: apps/knowledge/serializers/document.py:979 msgid "blank line" msgstr "空行" #: apps/knowledge/serializers/document.py:1140 msgid "Hit handling method is required" msgstr "命中處理方法是必需的" #: apps/knowledge/serializers/document.py:1142 msgid "The hit processing method must be directly_return|optimization" msgstr "命中處理方法必須是直接返回|優化" #: apps/knowledge/serializers/knowledge.py:51 #: apps/knowledge/serializers/knowledge.py:58 #: apps/knowledge/serializers/knowledge.py:67 #: apps/knowledge/serializers/knowledge.py:108 #: apps/shared/api/shared_knowledge.py:117 #: apps/shared/api/shared_knowledge.py:150 #: apps/shared/serializers/shared_knowledge.py:20 #: apps/shared/serializers/shared_knowledge.py:26 #: apps/shared/serializers/shared_knowledge.py:59 #: apps/shared/serializers/shared_knowledge.py:106 #: apps/xpack/serializers/dataset_lark_serializer.py:51 #: apps/xpack/serializers/dataset_lark_serializer.py:289 msgid "knowledge name" msgstr "知識庫名稱" #: apps/knowledge/serializers/knowledge.py:53 #: apps/knowledge/serializers/knowledge.py:60 #: apps/knowledge/serializers/knowledge.py:68 #: apps/knowledge/serializers/knowledge.py:110 #: apps/shared/api/shared_knowledge.py:124 #: apps/shared/api/shared_knowledge.py:157 #: apps/shared/serializers/shared_knowledge.py:21 #: apps/shared/serializers/shared_knowledge.py:27 #: apps/shared/serializers/shared_knowledge.py:60 #: apps/shared/serializers/shared_knowledge.py:108 #: apps/xpack/serializers/dataset_lark_serializer.py:53 #: apps/xpack/serializers/dataset_lark_serializer.py:291 msgid "knowledge description" msgstr "知識庫描述" #: apps/knowledge/serializers/knowledge.py:54 #: apps/knowledge/serializers/knowledge.py:61 #: apps/shared/serializers/shared_knowledge.py:22 #: apps/shared/serializers/shared_knowledge.py:28 msgid "knowledge embedding" msgstr "知識庫向量" #: apps/knowledge/serializers/knowledge.py:63 #: apps/shared/serializers/shared_knowledge.py:30 msgid "knowledge selector" msgstr "知識庫選擇器" #: apps/knowledge/serializers/knowledge.py:73 #: apps/xpack/serializers/dataset_lark_serializer.py:296 msgid "application id list" msgstr "智能體 ID 列表" #: apps/knowledge/serializers/knowledge.py:75 msgid "file size limit" msgstr "文件大小限制" #: apps/knowledge/serializers/knowledge.py:76 msgid "file count limit" msgstr "文件數量限制" #: apps/knowledge/serializers/knowledge.py:95 #: apps/knowledge/serializers/knowledge.py:638 msgid "query text" msgstr "查詢文本" #: apps/knowledge/serializers/knowledge.py:96 #: apps/knowledge/serializers/knowledge.py:639 msgid "top number" msgstr "Top 數量" #: apps/knowledge/serializers/knowledge.py:98 #: apps/knowledge/serializers/knowledge.py:641 msgid "search mode" msgstr "搜索模式" #: apps/knowledge/serializers/knowledge.py:113 msgid "knowledge scope" msgstr "知識庫範圍" #: apps/knowledge/serializers/knowledge.py:169 #: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464 msgid "Folder not found" msgstr "文件夾不存在" #: apps/knowledge/serializers/knowledge.py:236 #: apps/knowledge/serializers/knowledge.py:265 msgid "Failed to send the vectorization task, please try again later!" msgstr "發送向量化任務失敗,請稍後再試!" #: apps/knowledge/serializers/knowledge.py:315 #: apps/knowledge/serializers/knowledge.py:471 #: apps/knowledge/serializers/knowledge.py:533 #: apps/xpack/serializers/dataset_lark_serializer.py:82 #: apps/xpack/serializers/dataset_lark_serializer.py:340 msgid "Knowledge base name duplicate!" msgstr "知識庫名稱重複!" #: apps/knowledge/serializers/knowledge.py:341 #: apps/xpack/serializers/dataset_lark_serializer.py:359 #, python-brace-format msgid "Unknown application id {knowledge_id}, cannot be associated" msgstr "未知智能體 ID {knowledge_id},無法關聯" #: apps/knowledge/serializers/knowledge.py:449 #: apps/shared/serializers/shared_knowledge.py:62 #: apps/shared/serializers/shared_knowledge.py:110 #: apps/shared/serializers/shared_tool.py:46 #: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456 #: apps/xpack/serializers/dataset_lark_serializer.py:47 msgid "scope" msgstr "範圍" #: apps/knowledge/serializers/knowledge.py:460 msgid "" "The community version supports up to 50 knowledge bases. If you need more " "knowledge bases, please contact us (https://fit2cloud.com/)." msgstr "" "社區版支持最多50個知識庫,如需更多知識庫,請聯繫我們 (https://" "fit2cloud.com/)." #: apps/knowledge/serializers/knowledge.py:560 msgid "sync type" msgstr "同步類型" #: apps/knowledge/serializers/knowledge.py:562 msgid "The synchronization type only supports:replace|complete" msgstr "同步類型僅支持:replace|complete" #: apps/knowledge/serializers/knowledge.py:568 #: apps/knowledge/serializers/knowledge.py:649 msgid "id does not exist" msgstr "知識庫 ID 不存在" #: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76 msgid "id" msgstr "ID" #: apps/knowledge/serializers/paragraph.py:39 #: apps/knowledge/serializers/problem.py:27 #: apps/knowledge/serializers/problem.py:31 #: apps/knowledge/serializers/problem.py:206 msgid "content" msgstr "內容" #: apps/knowledge/serializers/paragraph.py:41 #: apps/knowledge/serializers/paragraph.py:48 #: apps/knowledge/serializers/paragraph.py:51 #: apps/knowledge/serializers/paragraph.py:65 #: apps/knowledge/serializers/paragraph.py:67 #: apps/knowledge/serializers/paragraph.py:323 msgid "section title" msgstr "章節標題" #: apps/knowledge/serializers/paragraph.py:44 #: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164 #: apps/xpack/serializers/system_api_key.py:11 msgid "Is active" msgstr "是否啟用" #: apps/knowledge/serializers/paragraph.py:56 #: apps/knowledge/serializers/paragraph.py:443 msgid "paragraph id list" msgstr "段落 ID 列表" #: apps/knowledge/serializers/paragraph.py:57 #: apps/knowledge/serializers/paragraph.py:72 #: apps/knowledge/serializers/paragraph.py:136 #: apps/knowledge/serializers/paragraph.py:350 #: apps/knowledge/serializers/paragraph.py:444 #: apps/knowledge/serializers/paragraph.py:561 #: apps/knowledge/serializers/problem.py:35 #: apps/knowledge/serializers/problem.py:50 msgid "paragraph id" msgstr "段落 ID" #: apps/knowledge/serializers/paragraph.py:77 #: apps/knowledge/serializers/paragraph.py:145 msgid "Paragraph id does not exist" msgstr "段落 ID 不存在" #: apps/knowledge/serializers/paragraph.py:108 msgid "Already associated, please do not associate again" msgstr "已關聯,請勿再次關聯" #: apps/knowledge/serializers/paragraph.py:181 msgid "Problem id does not exist" msgstr "問題 ID 不存在" #: apps/knowledge/serializers/paragraph.py:348 #: apps/knowledge/serializers/problem.py:26 #: apps/knowledge/serializers/problem.py:46 #: apps/knowledge/serializers/problem.py:56 #: apps/knowledge/serializers/problem.py:127 msgid "problem id" msgstr "問題 ID" #: apps/knowledge/serializers/paragraph.py:358 msgid "Paragraph does not exist" msgstr "段落不存在" #: apps/knowledge/serializers/paragraph.py:360 msgid "Problem does not exist" msgstr "問題不存在" #: apps/knowledge/serializers/paragraph.py:435 msgid "The task is being executed, please do not send it again." msgstr "任務正在執行,請勿重複發送。" #: apps/knowledge/serializers/paragraph.py:442 msgid "target document id" msgstr "目標文檔 ID" #: apps/knowledge/serializers/paragraph.py:453 msgid "The document to be migrated is consistent with the target document" msgstr "遷移的文檔與目標文檔一致" #: apps/knowledge/serializers/paragraph.py:455 msgid "The document id does not exist [{document_id}]" msgstr "以下文檔ID不存在: {error_id_list}" #: apps/knowledge/serializers/paragraph.py:459 msgid "The target document id does not exist [{document_id}]" msgstr "以下目標文檔ID不存在: {error_id_list}" #: apps/knowledge/serializers/paragraph.py:573 msgid "new_position must be an integer" msgstr "new_position 必須是整數" #: apps/knowledge/serializers/problem.py:45 #: apps/knowledge/serializers/problem.py:55 msgid "problem id list" msgstr "問題 ID 列表" #: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74 #, python-brace-format msgid "Failed to obtain vector model: {error} {traceback}" msgstr "向量模型獲取失敗: {error} {traceback}" #: apps/knowledge/task/embedding.py:103 #, python-brace-format msgid "Start--->Vectorized knowledge: {knowledge_id}" msgstr "開始--->向量知識庫: {knowledge_id}" #: apps/knowledge/task/embedding.py:107 #, python-brace-format msgid "Knowledge documentation: {document_names}" msgstr "知識庫文檔: {document_names}" #: apps/knowledge/task/embedding.py:120 #, python-brace-format msgid "End--->Vectorized knowledge: {knowledge_id}" msgstr "結束--->向量知識庫: {knowledge_id}" #: apps/knowledge/task/generate.py:106 #, python-brace-format msgid "" "Generate issue based on document: {document_id} error {error}{traceback}" msgstr "生成問題基於文檔: {document_id} 錯誤 {error}{traceback}" #: apps/knowledge/task/generate.py:110 #, python-brace-format msgid "End--->Generate problem: {document_id}" msgstr "結束--->生成問題: {document_id}" #: apps/knowledge/task/handler.py:121 #, python-brace-format msgid "Association problem failed {error}" msgstr "關聯問題失敗 {error}" #: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47 #, python-brace-format msgid "Start--->Start synchronization web knowledge base:{knowledge_id}" msgstr "開始--->開始同步 web 知識庫:{knowledge_id}" #: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51 #, python-brace-format msgid "End--->End synchronization web knowledge base:{knowledge_id}" msgstr "結束--->結束同步 web 知識庫:{knowledge_id}" #: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53 #, python-brace-format msgid "Synchronize web knowledge base:{knowledge_id} error{error}{traceback}" msgstr "同步 web 知識庫:{knowledge_id} 錯誤{error}{traceback}" #: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29 #: apps/knowledge/views/document.py:30 msgid "Create document" msgstr "創建文檔" #: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57 #: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104 #: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160 #: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209 #: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267 #: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323 #: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382 #: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441 #: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501 #: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553 #: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599 #: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664 #: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723 #: apps/knowledge/views/document.py:737 #: apps/xpack/views/dataset_lark_views.py:72 #: apps/xpack/views/dataset_lark_views.py:91 #: apps/xpack/views/dataset_lark_views.py:111 #: apps/xpack/views/dataset_lark_views.py:132 msgid "Knowledge Base/Documentation" msgstr "知識庫/文檔" #: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53 #: apps/knowledge/views/document.py:54 msgid "Get document" msgstr "獲取文檔" #: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80 #: apps/knowledge/views/document.py:81 msgid "Get document details" msgstr "文檔文檔詳情" #: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99 #: apps/knowledge/views/document.py:100 msgid "Modify document" msgstr "修改文檔" #: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124 #: apps/knowledge/views/document.py:125 msgid "Delete document" msgstr "刪除文檔" #: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155 #: apps/knowledge/views/document.py:156 msgid "Segmented document" msgstr "分段文檔" #: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187 #: apps/knowledge/views/document.py:188 msgid "Get a list of segment IDs" msgstr "獲取分段列表" #: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204 #: apps/knowledge/views/document.py:205 msgid "Modify document hit processing methods in batches" msgstr "批量修改文檔命中處理方法" #: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233 #: apps/knowledge/views/document.py:234 msgid "Synchronize web site types" msgstr "同步網站類型" #: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262 #: apps/knowledge/views/document.py:263 msgid "Refresh document vector library" msgstr "刷新文檔向量庫" #: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290 #: apps/knowledge/views/document.py:291 msgid "Cancel task" msgstr "取消任務" #: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318 #: apps/knowledge/views/document.py:319 msgid "Cancel tasks in batches" msgstr "批量取消任務" #: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347 #: apps/knowledge/views/document.py:348 msgid "Create documents in batches" msgstr "批量創建文檔" #: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377 #: apps/knowledge/views/document.py:378 msgid "Batch sync documents" msgstr "批量同步文檔" #: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407 #: apps/knowledge/views/document.py:408 msgid "Delete documents in batches" msgstr "批量刪除文檔" #: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437 msgid "Batch refresh document vector library" msgstr "批量刷新文檔向量庫" #: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467 #: apps/knowledge/views/document.py:468 msgid "Batch generate related problems" msgstr "批量生成相關問題" #: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497 #: apps/knowledge/views/document.py:498 msgid "Get document by pagination" msgstr "分頁獲取文檔" #: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524 msgid "Export document" msgstr "導出文檔" #: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550 msgid "Export Zip document" msgstr "導出 Zip 文檔" #: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576 msgid "Download source file" msgstr "下載源文件" #: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595 msgid "Migrate documents in batches" msgstr "批量遷移文檔" #: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628 #: apps/knowledge/views/document.py:629 #: apps/shared/views/shared_document.py:570 msgid "Create Web site documents" msgstr "創建網站文檔" #: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659 #: apps/knowledge/views/document.py:660 msgid "Import QA and create documentation" msgstr "導入問答並創建文檔" #: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690 #: apps/knowledge/views/document.py:691 msgid "Import tables and create documents" msgstr "導入表格並創建文檔" #: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720 msgid "Get QA template" msgstr "獲取問答模板" #: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734 msgid "Get form template" msgstr "獲取表格模板" #: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26 #: apps/knowledge/views/knowledge.py:27 msgid "Get knowledge by folder" msgstr "根據文件夾獲取知識庫" #: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59 #: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106 #: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156 #: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218 #: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266 #: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319 #: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369 #: apps/knowledge/views/knowledge.py:397 #: apps/xpack/views/dataset_lark_views.py:29 #: apps/xpack/views/dataset_lark_views.py:50 msgid "Knowledge Base" msgstr "知識庫" #: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54 #: apps/knowledge/views/knowledge.py:55 msgid "Edit knowledge" msgstr "修改知識庫" #: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78 #: apps/knowledge/views/knowledge.py:79 msgid "Delete knowledge" msgstr "刪除知識庫" #: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102 #: apps/knowledge/views/knowledge.py:103 msgid "Get knowledge" msgstr "獲取知識庫" #: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123 #: apps/knowledge/views/knowledge.py:124 msgid "Get the knowledge base paginated list" msgstr "獲取知識庫分頁列表" #: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151 #: apps/knowledge/views/knowledge.py:152 msgid "Synchronize the knowledge base of the website" msgstr "同步網站知識庫" #: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183 #: apps/knowledge/views/knowledge.py:184 msgid "Hit test list" msgstr "命中測試列表" #: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213 #: apps/knowledge/views/knowledge.py:214 msgid "Re-vectorize" msgstr "重新向量化" #: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239 msgid "Export knowledge base" msgstr "導出知識庫" #: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263 msgid "Export knowledge base containing images" msgstr "導出包含圖片的知識庫" #: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288 #: apps/knowledge/views/knowledge.py:289 msgid "Generate related" msgstr "生成相關" #: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315 #: apps/knowledge/views/knowledge.py:316 msgid "Get model for knowledge base" msgstr "獲取知識庫模型" #: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339 #: apps/knowledge/views/knowledge.py:340 msgid "Get embedding model for knowledge base" msgstr "獲取知識庫向量模型" #: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364 #: apps/knowledge/views/knowledge.py:365 msgid "Create base knowledge" msgstr "創建知識庫" #: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392 #: apps/knowledge/views/knowledge.py:393 msgid "Create web knowledge" msgstr "創建 web 知識庫" #: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25 #: apps/knowledge/views/paragraph.py:26 msgid "Paragraph list" msgstr "段落列表" #: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53 #: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102 #: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167 #: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224 #: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289 #: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351 #: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415 msgid "Knowledge Base/Documentation/Paragraph" msgstr "知識庫/文檔/段落" #: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49 msgid "Create Paragraph" msgstr "創建段落" #: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77 #: apps/knowledge/views/paragraph.py:78 msgid "Batch Paragraph" msgstr "批量段落" #: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98 msgid "Migrate paragraphs in batches" msgstr "批量遷移段落" #: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133 #: apps/knowledge/views/paragraph.py:134 msgid "Batch Generate Related" msgstr "批量生成相關" #: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162 #: apps/knowledge/views/paragraph.py:163 msgid "Modify paragraph data" msgstr "修改段落數據" #: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195 #: apps/knowledge/views/paragraph.py:196 msgid "Get paragraph details" msgstr "獲取段落詳情" #: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220 #: apps/knowledge/views/paragraph.py:221 msgid "Delete paragraph" msgstr "刪除段落" #: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254 #: apps/knowledge/views/paragraph.py:255 msgid "Add associated questions" msgstr "添加關聯問題" #: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285 #: apps/knowledge/views/paragraph.py:286 msgid "Get a list of paragraph questions" msgstr "獲取段落問題列表" #: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311 #: apps/knowledge/views/paragraph.py:312 msgid "Disassociation issue" msgstr "取消關聯問題" #: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346 #: apps/knowledge/views/paragraph.py:347 msgid "Related questions" msgstr "關聯問題" #: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381 #: apps/knowledge/views/paragraph.py:382 msgid "Get paragraph list by pagination" msgstr "獲取段落列表" #: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410 #: apps/knowledge/views/paragraph.py:411 #: apps/resource_manage/views/paragraph.py:364 #: apps/resource_manage/views/paragraph.py:365 #: apps/resource_manage/views/paragraph.py:366 #: apps/shared/views/shared_paragraph.py:365 #: apps/shared/views/shared_paragraph.py:366 #: apps/shared/views/shared_paragraph.py:367 msgid "Adjust paragraph position" msgstr "調整段落位置" #: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24 #: apps/knowledge/views/problem.py:25 msgid "Question list" msgstr "問題列表" #: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53 #: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104 #: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157 #: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216 msgid "Knowledge Base/Documentation/Paragraph/Question" msgstr "知識庫/文檔/段落/問題" #: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48 #: apps/knowledge/views/problem.py:49 msgid "Create question" msgstr "創建問題" #: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74 #: apps/knowledge/views/problem.py:75 msgid "Get a list of associated paragraphs" msgstr "獲取關聯段落列表" #: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99 #: apps/knowledge/views/problem.py:100 msgid "Batch associated paragraphs" msgstr "批量關聯段落" #: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126 #: apps/knowledge/views/problem.py:127 msgid "Batch deletion issues" msgstr "批量刪除問題" #: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153 #: apps/knowledge/views/problem.py:154 msgid "Delete question" msgstr "刪除問題" #: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181 #: apps/knowledge/views/problem.py:182 msgid "Modify question" msgstr "修改問題" #: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212 #: apps/knowledge/views/problem.py:213 msgid "Get the list of questions by page" msgstr "分頁獲取問題列表" #: apps/maxkb/settings/base.py:101 msgid "Intelligent customer service platform" msgstr "强大易用的企業級智能體平臺" #: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17 #: apps/models_provider/api/provide.py:23 #: apps/models_provider/api/provide.py:28 #: apps/models_provider/api/provide.py:30 #: apps/models_provider/api/provide.py:82 #: apps/models_provider/serializers/model_serializer.py:40 #: apps/models_provider/serializers/model_serializer.py:215 #: apps/models_provider/serializers/model_serializer.py:253 #: apps/models_provider/serializers/model_serializer.py:318 #: apps/models_provider/serializers/model_serializer.py:393 #: apps/shared/api/shared_model.py:18 #: apps/shared/serializers/shared_model.py:111 msgid "model name" msgstr "模型名稱" #: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29 #: apps/models_provider/api/provide.py:70 #: apps/models_provider/api/provide.py:98 #: apps/models_provider/serializers/model_serializer.py:42 #: apps/models_provider/serializers/model_serializer.py:217 #: apps/models_provider/serializers/model_serializer.py:255 #: apps/models_provider/serializers/model_serializer.py:319 #: apps/models_provider/serializers/model_serializer.py:394 #: apps/shared/api/shared_model.py:25 #: apps/shared/serializers/shared_model.py:112 msgid "model type" msgstr "模型類型" #: apps/models_provider/api/model.py:51 #: apps/models_provider/serializers/model_serializer.py:43 #: apps/models_provider/serializers/model_serializer.py:219 #: apps/models_provider/serializers/model_serializer.py:256 #: apps/models_provider/serializers/model_serializer.py:320 #: apps/models_provider/serializers/model_serializer.py:395 #: apps/shared/api/shared_model.py:32 #: apps/shared/serializers/shared_model.py:113 msgid "base model" msgstr "基礎模型" #: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18 #: apps/models_provider/api/provide.py:38 #: apps/models_provider/api/provide.py:76 #: apps/models_provider/api/provide.py:104 #: apps/models_provider/api/provide.py:126 #: apps/models_provider/serializers/model_serializer.py:41 #: apps/models_provider/serializers/model_serializer.py:254 #: apps/models_provider/serializers/model_serializer.py:321 #: apps/models_provider/serializers/model_serializer.py:396 #: apps/shared/api/shared_model.py:39 #: apps/shared/serializers/shared_model.py:114 msgid "provider" msgstr "供應商" #: apps/models_provider/api/model.py:65 #: apps/models_provider/serializers/model_serializer.py:322 #: apps/models_provider/serializers/model_serializer.py:397 #: apps/shared/api/shared_model.py:46 #: apps/shared/serializers/shared_model.py:115 msgid "create user" msgstr "創建用戶" #: apps/models_provider/api/provide.py:19 #: apps/xpack/serializers/application_setting_serializer.py:41 #: apps/xpack/serializers/system_params.py:21 msgid "icon" msgstr "圖標" #: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134 msgid "input type" msgstr "輸入類型" #: apps/models_provider/api/provide.py:35 msgid "label" msgstr "標籤" #: apps/models_provider/api/provide.py:36 msgid "text field" msgstr "文本欄位" #: apps/models_provider/api/provide.py:37 msgid "value field" msgstr "值" #: apps/models_provider/api/provide.py:39 msgid "method" msgstr "方法" #: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119 #: apps/tools/serializers/tool.py:133 msgid "required" msgstr "必填" #: apps/models_provider/api/provide.py:41 msgid "default value" msgstr "默認值" #: apps/models_provider/api/provide.py:42 msgid "relation show field dict" msgstr "關係顯示欄位" #: apps/models_provider/api/provide.py:43 msgid "relation trigger field dict" msgstr "關係觸發欄位" #: apps/models_provider/api/provide.py:44 msgid "trigger type" msgstr "觸發類型" #: apps/models_provider/api/provide.py:45 msgid "attrs" msgstr "屬性" #: apps/models_provider/api/provide.py:46 msgid "props info" msgstr "props 信息" #: apps/models_provider/base_model_provider.py:60 msgid "Model type cannot be empty" msgstr "模型類型不能為空" #: apps/models_provider/base_model_provider.py:85 msgid "The current platform does not support downloading models" msgstr "當前平臺不支持下載模型" #: apps/models_provider/base_model_provider.py:143 msgid "LLM" msgstr "大語言模型" #: apps/models_provider/base_model_provider.py:144 msgid "Embedding Model" msgstr "向量模型" #: apps/models_provider/base_model_provider.py:145 msgid "Speech2Text" msgstr "語音識別" #: apps/models_provider/base_model_provider.py:146 msgid "TTS" msgstr "語音合成" #: apps/models_provider/base_model_provider.py:147 msgid "Vision Model" msgstr "視覺模型" #: apps/models_provider/base_model_provider.py:148 msgid "Image Generation" msgstr "圖片生成" #: apps/models_provider/base_model_provider.py:149 msgid "Rerank" msgstr "重排模型" #: apps/models_provider/base_model_provider.py:223 msgid "The model does not support" msgstr "模型不支持" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42 msgid "" "With the GTE-Rerank text sorting series model developed by Alibaba Tongyi " "Lab, developers can integrate high-quality text retrieval and sorting " "through the LlamaIndex framework." msgstr "" "阿里巴巴通義實驗室開發的GTE-Rerank文本排序系列模型,開發者可以通過LlamaIndex" "框架進行集成高質量文本檢索、排序。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45 msgid "" "Chinese (including various dialects such as Cantonese), English, Japanese, " "and Korean support free switching between multiple languages." msgstr "中文(含粵語等各種方言)、英文、日語、韓語支持多個語種自由切換" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48 msgid "" "CosyVoice is based on a new generation of large generative speech models, " "which can predict emotions, intonation, rhythm, etc. based on context, and " "has better anthropomorphic effects." msgstr "" "CosyVoice基於新一代生成式語音大模型,能根據上下文預測情緒、語調、韻律等,具有" "更好的擬人效果" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51 msgid "" "Universal text vector is Tongyi Lab's multi-language text unified vector " "model based on the LLM base. It provides high-level vector services for " "multiple mainstream languages around the world and helps developers quickly " "convert text data into high-quality vector data." msgstr "" "通用文本向量,是通義實驗室基於LLM底座的多語言文本統一向量模型,面向全球多個主" "流語種,提供高水準的向量服務,幫助開發者將文本數據快速轉換為高質量的向量數" "據。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69 msgid "" "Tongyi Wanxiang - a large image model for text generation, supports " "bilingual input in Chinese and English, and supports the input of reference " "pictures for reference content or reference style migration. Key styles " "include but are not limited to watercolor, oil painting, Chinese painting, " "sketch, flat illustration, two-dimensional, and 3D. Cartoon." msgstr "" "通義萬相-文本生成圖像大模型,支持中英文雙語輸入,支持輸入參考圖片進行參考內容" "或者參考風格遷移,重點風格包括但不限於水彩、油畫、中國畫、素描、扁平插畫、二" "次元、3D卡通。" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95 msgid "Alibaba Cloud Bailian" msgstr "阿里雲百鍊" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/azure_model_provider/credential/image.py:40 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:69 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57 #: apps/models_provider/impl/gemini_model_provider/model/stt.py:43 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/openai_model_provider/credential/image.py:35 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:59 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:37 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51 #: apps/models_provider/impl/tencent_model_provider/model/tti.py:54 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57 #: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:76 #: apps/models_provider/impl/xf_model_provider/model/tts.py:101 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:32 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34 #: apps/models_provider/impl/xinference_model_provider/model/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56 #: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49 msgid "Hello" msgstr "你好" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/azure_model_provider/credential/image.py:30 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:59 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:23 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:58 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:41 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:19 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/openai_model_provider/credential/image.py:25 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:48 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:22 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:61 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:40 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:28 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:22 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27 #: apps/models_provider/impl/xf_model_provider/credential/image.py:29 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:66 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:24 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:22 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40 #, python-brace-format msgid "{model_type} Model type is not supported" msgstr "{model_type} 模型類型不支持" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98 #, python-brace-format msgid "{key} is required" msgstr "{key} 是必填項" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121 #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61 #: apps/models_provider/impl/azure_model_provider/credential/image.py:50 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:40 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:77 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:58 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:42 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:45 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/openai_model_provider/credential/image.py:45 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:67 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:39 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:80 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:58 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66 #: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:47 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:42 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68 #: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38 #: apps/models_provider/impl/xf_model_provider/credential/image.py:50 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:84 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:41 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:65 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59 #, python-brace-format msgid "" "Verification failed, please check whether the parameters are correct: {error}" msgstr "認證失敗,請檢查參數是否正確:{error}" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:23 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:23 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:22 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:41 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22 msgid "Temperature" msgstr "溫度" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:24 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:24 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23 #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:23 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:42 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23 msgid "" "Higher values make the output more random, while lower values make it more " "focused and deterministic" msgstr "較高的數值會使輸出更加隨機,而較低的數值會使其更加集中和確定" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:32 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:43 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:31 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:50 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31 msgid "Output the maximum Tokens" msgstr "輸出最大Token數" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31 msgid "Specify the maximum number of tokens that the model can generate." msgstr "指定模型可以生成的最大 tokens 數" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43 #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74 msgid "API URL" msgstr "" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:15 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15 msgid "Image size" msgstr "图片尺寸" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:15 msgid "Specify the size of the generated image, such as: 1024x1024" msgstr "指定生成圖片的尺寸, 如: 1024x1024" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:43 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41 msgid "Number of pictures" msgstr "圖片數量" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:40 msgid "Specify the number of generated images" msgstr "指定生成圖片的數量" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Style" msgstr "風格" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44 msgid "Specify the style of generated images" msgstr "指定生成圖片的風格" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48 msgid "Default value, the image style is randomly output by the model" msgstr "默認值,圖片風格由模型隨機輸出" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49 msgid "photography" msgstr "攝影" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50 msgid "Portraits" msgstr "人像寫真" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51 msgid "3D cartoon" msgstr "3D卡通" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52 msgid "animation" msgstr "動畫" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53 msgid "painting" msgstr "油畫" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54 msgid "watercolor" msgstr "水彩" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55 msgid "sketch" msgstr "素描" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56 msgid "Chinese painting" msgstr "中國畫" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57 msgid "flat illustration" msgstr "扁平插畫" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 msgid "Timbre" msgstr "音色" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 msgid "Chinese sounds can support mixed scenes of Chinese and English" msgstr "中文音色支持中英文混合場景" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26 msgid "Long Xiaochun" msgstr "龍小淳" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27 msgid "Long Xiaoxia" msgstr "龍小夏" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28 msgid "Long Xiaochen" msgstr "龍小誠" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29 msgid "Long Xiaobai" msgstr "龍小白" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30 msgid "Long Laotie" msgstr "龍老鐵" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31 msgid "Long Shu" msgstr "龍書" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32 msgid "Long Shuo" msgstr "龍碩" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33 msgid "Long Jing" msgstr "龍婧" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34 msgid "Long Miao" msgstr "龍妙" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35 msgid "Long Yue" msgstr "龍悅" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36 msgid "Long Yuan" msgstr "龍媛" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37 msgid "Long Fei" msgstr "龍飛" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38 msgid "Long Jielidou" msgstr "龍傑力豆" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39 msgid "Long Tong" msgstr "龍彤" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40 msgid "Long Xiang" msgstr "龍祥" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "Speaking speed" msgstr "語速" #: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47 msgid "[0.5, 2], the default is 1, usually one decimal place is enough" msgstr "[0.5,2],默認為1,通常一位小數就足夠了" #: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28 #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52 #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32 #: apps/models_provider/impl/azure_model_provider/credential/image.py:35 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:64 #: apps/models_provider/impl/azure_model_provider/credential/stt.py:28 #: apps/models_provider/impl/azure_model_provider/credential/tti.py:63 #: apps/models_provider/impl/azure_model_provider/credential/tts.py:46 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/gemini_model_provider/credential/image.py:27 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52 #: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52 #: apps/models_provider/impl/local_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63 #: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/openai_model_provider/credential/image.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:53 #: apps/models_provider/impl/openai_model_provider/credential/stt.py:27 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:66 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:45 #: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32 #: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52 #: apps/models_provider/impl/tencent_model_provider/credential/image.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/vllm_model_provider/credential/image.py:27 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72 #: apps/models_provider/impl/xf_model_provider/credential/image.py:34 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:71 #: apps/models_provider/impl/xf_model_provider/credential/stt.py:29 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:52 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40 #: apps/models_provider/impl/xinference_model_provider/credential/image.py:27 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59 #: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44 #: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51 #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45 #, python-brace-format msgid "{key} is required" msgstr "{key} 是必填項" #: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:33 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:44 #: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32 #: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32 #: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30 #: apps/models_provider/impl/openai_model_provider/credential/llm.py:33 #: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25 #: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32 #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:32 #: apps/models_provider/impl/xf_model_provider/credential/llm.py:51 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25 #: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32 msgid "Specify the maximum number of tokens that the model can generate" msgstr "指定模型可以生成的最大 tokens 數" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36 msgid "" "An update to Claude 2 that doubles the context window and improves " "reliability, hallucination rates, and evidence-based accuracy in long " "documents and RAG contexts." msgstr "" "Claude 2 的更新,採用雙倍的上下文窗口,並在長文檔和 RAG 上下文中提高可靠性、" "幻覺率和循證準確性。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43 msgid "" "Anthropic is a powerful model that can handle a variety of tasks, from " "complex dialogue and creative content generation to detailed command " "obedience." msgstr "" "Anthropic 功能強大的模型,可處理各種任務,從複雜的對話和創意內容生成到詳細的" "指令服從。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50 msgid "" "The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-" "instant responsiveness. The model can answer simple queries and requests " "quickly. Customers will be able to build seamless AI experiences that mimic " "human interactions. Claude 3 Haiku can process images and return text " "output, and provides 200K context windows." msgstr "" "Claude 3 Haiku 是 Anthropic 最快速、最緊湊的模型,具有近乎即時的響應能力。該" "模型可以快速回答簡單的查詢和請求。客戶將能夠構建模仿人類交互的無縫人工智能體" "驗。 Claude 3 Haiku 可以處理圖像和返回文本輸出,並且提供 200K 上下文窗口。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57 msgid "" "The Claude 3 Sonnet model from Anthropic strikes the ideal balance between " "intelligence and speed, especially when it comes to handling enterprise " "workloads. This model offers maximum utility while being priced lower than " "competing products, and it's been engineered to be a solid choice for " "deploying AI at scale." msgstr "" "Anthropic 推出的 Claude 3 Sonnet 模型在智能和速度之間取得理想的平衡,尤其是在" "處理企業工作負載方面。該模型提供最大的效用,同時價格低於競爭產品,並且其經過" "精心設計,是大規模部署人工智慧的可靠選擇。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64 msgid "" "The Claude 3.5 Sonnet raises the industry standard for intelligence, " "outperforming competing models and the Claude 3 Opus in extensive " "evaluations, with the speed and cost-effectiveness of our mid-range models." msgstr "" "Claude 3.5 Sonnet提高了智能的行業標準,在廣泛的評估中超越了競爭對手的型號和" "Claude 3 Opus,具有我們中端型號的速度和成本效益。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71 msgid "" "A faster, more affordable but still very powerful model that can handle a " "range of tasks including casual conversation, text analysis, summarization " "and document question answering." msgstr "" "一種更快速、更實惠但仍然非常強大的模型,它可以處理一系列任務,包括隨意對話、" "文本分析、摘要和文檔問題回答。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78 msgid "" "Titan Text Premier is the most powerful and advanced model in the Titan Text " "series, designed to deliver exceptional performance for a variety of " "enterprise applications. With its cutting-edge features, it delivers greater " "accuracy and outstanding results, making it an excellent choice for " "organizations looking for a top-notch text processing solution." msgstr "" "Titan Text Premier 是 Titan Text 系列中功能強大且先進的型號,旨在為各種企業應" "用程序提供卓越的性能。憑藉其尖端功能,它提供了更高的準確性和出色的結果,使其" "成為尋求一流文本處理解決方案的組織的絕佳選擇。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85 msgid "" "Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-" "tuning English-language tasks, including summarization and copywriting, " "where customers require smaller, more cost-effective, and highly " "customizable models." msgstr "" "Amazon Titan Text Lite 是一種輕量級的高效模型,非常適合英語任務的微調,包括摘" "要和文案寫作等,在這種場景下,客戶需要更小、更經濟高效且高度可定製的模型" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91 msgid "" "Amazon Titan Text Express has context lengths of up to 8,000 tokens, making " "it ideal for a variety of high-level general language tasks, such as open-" "ended text generation and conversational chat, as well as support in " "retrieval-augmented generation (RAG). At launch, the model is optimized for " "English, but other languages are supported." msgstr "" "Amazon Titan Text Express 的上下文長度長達 8000 個 tokens,因而非常適合各種高" "級常規語言任務,例如開放式文本生成和對話式聊天,以及檢索增強生成(RAG)中的支" "持。在發布時,該模型針對英語進行了優化,但也支持其他語言。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97 msgid "" "7B dense converter for rapid deployment and easy customization. Small in " "size yet powerful in a variety of use cases. Supports English and code, as " "well as 32k context windows." msgstr "" "7B 密集型轉換器,可快速部署,易於定製。體積雖小,但功能強大,適用於各種用例。" "支持英語和代碼,以及 32k 的上下文窗口。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103 msgid "" "Advanced Mistral AI large-scale language model capable of handling any " "language task, including complex multilingual reasoning, text understanding, " "transformation, and code generation." msgstr "" "先進的 Mistral AI 大型語言模型,能夠處理任何語言任務,包括複雜的多語言推理、" "文本理解、轉換和代碼生成。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109 msgid "" "Ideal for content creation, conversational AI, language understanding, R&D, " "and enterprise applications" msgstr "非常適合內容創作、對話式人工智慧、語言理解、研發和企業智能體" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115 msgid "" "Ideal for limited computing power and resources, edge devices, and faster " "training times." msgstr "非常適合有限的計算能力和資源、邊緣設備和更快的訓練時間。" #: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123 msgid "" "Titan Embed Text is the largest embedding model in the Amazon Titan Embed " "series and can handle various text embedding tasks, such as text " "classification, text similarity calculation, etc." msgstr "" "Titan Embed Text 是 Amazon Titan Embed 系列中最大的嵌入模型,可以處理各種文本" "嵌入任務,如文本分類、文本相似度計算等。" #: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28 #: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47 #, python-brace-format msgid "The following fields are required: {keys}" msgstr "以下欄位是必填項: {keys}" #: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44 #: apps/models_provider/impl/azure_model_provider/credential/llm.py:76 msgid "Verification failed, please check whether the parameters are correct" msgstr "認證失敗,請檢查參數是否正確" #: apps/models_provider/impl/azure_model_provider/credential/tti.py:28 #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28 msgid "Picture quality" msgstr "圖片質量" #: apps/models_provider/impl/azure_model_provider/credential/tts.py:17 #: apps/models_provider/impl/openai_model_provider/credential/tts.py:17 msgid "" "Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) " "to find one that suits your desired tone and audience. The current voiceover " "is optimized for English." msgstr "" "嘗試不同的聲音(合金、回聲、寓言、縞瑪瑙、新星和閃光),找到一種適合您所需的" "音調和聽眾的聲音。當前的語音針對英語進行了優化。" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24 msgid "Good at common conversational tasks, supports 32K contexts" msgstr "擅長通用對話任務,支持 32K 上下文" #: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29 msgid "Good at handling programming tasks, supports 16K contexts" msgstr "擅長處理編程任務,支持 16K 上下文" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32 msgid "Latest Gemini 1.0 Pro model, updated with Google update" msgstr "最新的 Gemini 1.0 Pro 模型,更新了 Google 更新" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36 msgid "Latest Gemini 1.0 Pro Vision model, updated with Google update" msgstr "最新的Gemini 1.0 Pro Vision模型,隨Google更新而更新" #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54 #: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58 msgid "Latest Gemini 1.5 Flash model, updated with Google updates" msgstr "最新的Gemini 1.5 Flash模型,隨Google更新而更新" #: apps/models_provider/impl/gemini_model_provider/model/stt.py:53 msgid "convert audio to text" msgstr "將音頻轉換為文本" #: apps/models_provider/impl/local_model_provider/credential/embedding.py:53 #: apps/models_provider/impl/local_model_provider/credential/reranker.py:54 msgid "Model catalog" msgstr "模型目錄" #: apps/models_provider/impl/local_model_provider/local_model_provider.py:39 msgid "local model" msgstr "本地模型" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:23 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44 msgid "API domain name is invalid" msgstr "API 域名無效" #: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35 #: apps/models_provider/impl/ollama_model_provider/credential/image.py:28 #: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53 #: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40 #: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47 #: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30 #: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48 msgid "The model does not exist, please download the model first" msgstr "模型不存在,請先下載模型" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 7B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一組經過預訓練和微調的生成文本模型,其規模從 70 億到 700 億個不等。" "這是 7B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 13B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一組經過預訓練和微調的生成文本模型,其規模從 70 億到 700 億個不等。" "這是 13B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64 msgid "" "Llama 2 is a set of pretrained and fine-tuned generative text models ranging " "in size from 7 billion to 70 billion. This is a repository of 70B pretrained " "models. Links to other models can be found in the index at the bottom." msgstr "" "Llama 2 是一組經過預訓練和微調的生成文本模型,其規模從 70 億到 700 億個不等。" "這是 70B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68 msgid "" "Since the Chinese alignment of Llama2 itself is weak, we use the Chinese " "instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so " "that it has strong Chinese conversation capabilities." msgstr "" "由於Llama2本身的中文對齊較弱,我們採用中文指令集,對meta-llama/Llama-2-13b-" "chat-hf進行LoRA微調,使其具備較強的中文對話能力。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72 msgid "" "Meta Llama 3: The most capable public product LLM to date. 8 billion " "parameters." msgstr "Meta Llama 3:迄今為止最有能力的公開產品LLM。80億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76 msgid "" "Meta Llama 3: The most capable public product LLM to date. 70 billion " "parameters." msgstr "Meta Llama 3:迄今為止最有能力的公開產品LLM。700億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80 msgid "" "Compared with previous versions, qwen 1.5 0.5b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 500 million parameters." msgstr "" "qwen 1.5 0.5b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有" "顯著增強。所有規模的模型都支持32768個tokens的上下文長度。5億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84 msgid "" "Compared with previous versions, qwen 1.5 1.8b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 1.8 billion parameters." msgstr "" "qwen 1.5 1.8b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有" "顯著增強。所有規模的模型都支持32768個tokens的上下文長度。18億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88 msgid "" "Compared with previous versions, qwen 1.5 4b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "4 billion parameters." msgstr "" "qwen 1.5 4b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有顯" "著增強。所有規模的模型都支持32768個tokens的上下文長度。40億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93 msgid "" "Compared with previous versions, qwen 1.5 7b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "7 billion parameters." msgstr "" "qwen 1.5 7b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有顯" "著增強。所有規模的模型都支持32768個tokens的上下文長度。70億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97 msgid "" "Compared with previous versions, qwen 1.5 14b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "14 billion parameters." msgstr "" "qwen 1.5 14b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有顯" "著增強。所有規模的模型都支持32768個tokens的上下文長度。140億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101 msgid "" "Compared with previous versions, qwen 1.5 32b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "32 billion parameters." msgstr "" "qwen 1.5 32b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有顯" "著增強。所有規模的模型都支持32768個tokens的上下文長度。320億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105 msgid "" "Compared with previous versions, qwen 1.5 72b has significantly enhanced the " "model's alignment with human preferences and its multi-language processing " "capabilities. Models of all sizes support a context length of 32768 tokens. " "72 billion parameters." msgstr "" "qwen 1.5 72b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有顯" "著增強。所有規模的模型都支持32768個tokens的上下文長度。720億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109 msgid "" "Compared with previous versions, qwen 1.5 110b has significantly enhanced " "the model's alignment with human preferences and its multi-language " "processing capabilities. Models of all sizes support a context length of " "32768 tokens. 110 billion parameters." msgstr "" "qwen 1.5 110b 相較於以往版本,模型與人類偏好的對齊程度以及多語言處理能力上有" "顯著增強。所有規模的模型都支持32768個tokens的上下文長度。1100億參數。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193 msgid "" "Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open " "model." msgstr "Phi-3 Mini是Microsoft的3.8B參數,輕量級,最先進的開放模型。" #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162 #: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197 msgid "" "A high-performance open embedding model with a large token context window." msgstr "一個具有大 tokens上下文窗口的高性能開放嵌入模型。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:16 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 " "or 1792x1024 pixels." msgstr "" "圖像生成端點允許您根據文本提示創建原始圖像。使用 DALL·E 3 時,圖像的尺寸可以" "為 1024x1024、1024x1792 或 1792x1024 像素。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:29 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29 msgid "" " \n" "By default, images are produced in standard quality, but with DALL·E 3 you " "can set quality: \"hd\" to enhance detail. Square, standard quality images " "are generated fastest.\n" " " msgstr "" "默認情況下,圖像以標準質量生成,但使用 DALL·E 3 時,您可以設置質量:「hd」以增" "強細節。方形、標準質量的圖像生成速度最快。" #: apps/models_provider/impl/openai_model_provider/credential/tti.py:44 #: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44 msgid "" "You can use DALL·E 3 to request 1 image at a time (requesting more images by " "issuing parallel requests), or use DALL·E 2 with the n parameter to request " "up to 10 images at a time." msgstr "" "您可以使用 DALL·E 3 一次請求 1 個圖像(通過發出並行請求來請求更多圖像),或者" "使用帶有 n 參數的 DALL·E 2 一次最多請求 10 個圖像。" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119 #: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118 msgid "The latest gpt-3.5-turbo, updated with OpenAI adjustments" msgstr "最新的gpt-3.5-turbo,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38 msgid "Latest gpt-4, updated with OpenAI adjustments" msgstr "最新的gpt-4,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99 msgid "" "The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI " "adjustments" msgstr "最新的GPT-4o,比gpt-4-turbo更便宜、更快,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43 #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102 msgid "" "The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI " "adjustments" msgstr "最新的gpt-4o-mini,比gpt-4o更便宜、更快,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46 msgid "The latest gpt-4-turbo, updated with OpenAI adjustments" msgstr "最新的gpt-4-turbo,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49 msgid "The latest gpt-4-turbo-preview, updated with OpenAI adjustments" msgstr "最新的gpt-4-turbo-preview,隨OpenAI調整而更新" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53 msgid "" "gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 " "tokens" msgstr "2024年1月25日的gpt-3.5-turbo快照,支持上下文長度16,385 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57 msgid "" "gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 " "tokens" msgstr "2023年11月6日的gpt-3.5-turbo快照,支持上下文長度16,385 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61 msgid "" "[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June " "13, 2024" msgstr "[Legacy] 2023年6月13日的gpt-3.5-turbo快照,將於2024年6月13日棄用" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65 msgid "" "gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens" msgstr "2024年5月13日的gpt-4o快照,支持上下文長度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69 msgid "" "gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 " "tokens" msgstr "2024年4月9日的gpt-4-turbo快照,支持上下文長度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72 msgid "" "gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 " "tokens" msgstr "2024年1月25日的gpt-4-turbo快照,支持上下文長度128,000 tokens" #: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75 msgid "" "gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 " "tokens" msgstr "2023年11月6日的gpt-4-turbo快照,支持上下文長度128,000 tokens" #: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58 msgid "Tencent Cloud" msgstr "騰訊雲" #: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41 #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88 #, python-brace-format msgid "{keys} is required" msgstr "{keys} 是必填項" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "painting style" msgstr "繪畫風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14 msgid "If not passed, the default value is 201 (Japanese anime style)" msgstr "如果未傳遞,則默認值為201(日本動漫風格)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18 msgid "Not limited to style" msgstr "不限於風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19 msgid "ink painting" msgstr "水墨畫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20 msgid "concept art" msgstr "概念藝術" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21 msgid "Oil painting 1" msgstr "油畫1" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22 msgid "Oil Painting 2 (Van Gogh)" msgstr "油畫2(梵谷)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23 msgid "watercolor painting" msgstr "水彩畫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24 msgid "pixel art" msgstr "像素畫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25 msgid "impasto style" msgstr "厚塗風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26 msgid "illustration" msgstr "插圖" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27 msgid "paper cut style" msgstr "剪紙風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28 msgid "Impressionism 1 (Monet)" msgstr "印象派1(莫奈)" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29 msgid "Impressionism 2" msgstr "印象派2" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31 msgid "classical portraiture" msgstr "古典肖像畫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32 msgid "black and white sketch" msgstr "黑白素描畫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33 msgid "cyberpunk" msgstr "賽博朋克" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34 msgid "science fiction style" msgstr "科幻風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35 msgid "dark style" msgstr "暗黑風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37 msgid "vaporwave" msgstr "蒸汽波" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38 msgid "Japanese animation" msgstr "日系動漫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39 msgid "monster style" msgstr "怪獸風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40 msgid "Beautiful ancient style" msgstr "唯美古風" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41 msgid "retro anime" msgstr "復古動漫" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42 msgid "Game cartoon hand drawing" msgstr "遊戲卡通手繪" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43 msgid "Universal realistic style" msgstr "通用寫實風格" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "Generate image resolution" msgstr "生成圖像解析度" #: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50 msgid "If not transmitted, the default value is 768:768." msgstr "不傳默認使用768:768。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38 msgid "" "The most effective version of the current hybrid model, the trillion-level " "parameter scale MOE-32K long article model. Reaching the absolute leading " "level on various benchmarks, with complex instructions and reasoning, " "complex mathematical capabilities, support for function call, and " "application focus optimization in fields such as multi-language translation, " "finance, law, and medical care" msgstr "" "當前混元模型中效果最優版本,萬億級參數規模 MOE-32K 長文模型。在各種 " "benchmark 上達到絕對領先的水平,複雜指令和推理,具備複雜數學能力,支持 " "functioncall,在多語言翻譯、金融法律醫療等領域智能體重點優化" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45 msgid "" "A better routing strategy is adopted to simultaneously alleviate the " "problems of load balancing and expert convergence. For long articles, the " "needle-in-a-haystack index reaches 99.9%" msgstr "" "採用更優的路由策略,同時緩解了負載均衡和專家趨同的問題。長文方面,大海撈針指" "標達到99.9%" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51 msgid "" "Upgraded to MOE structure, the context window is 256k, leading many open " "source models in multiple evaluation sets such as NLP, code, mathematics, " "industry, etc." msgstr "" "升級為 MOE 結構,上下文窗口為 256k ,在 NLP,代碼,數學,行業等多項評測集上領" "先眾多開源模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57 msgid "" "Hunyuan's latest version of the role-playing model, a role-playing model " "launched by Hunyuan's official fine-tuning training, is based on the Hunyuan " "model combined with the role-playing scene data set for additional training, " "and has better basic effects in role-playing scenes." msgstr "" "混元最新版角色扮演模型,混元官方精調訓練推出的角色扮演模型,基於混元模型結合" "角色扮演場景數據集進行增訓,在角色扮演場景具有更好的基礎效果" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63 msgid "" "Hunyuan's latest MOE architecture FunctionCall model has been trained with " "high-quality FunctionCall data and has a context window of 32K, leading in " "multiple dimensions of evaluation indicators." msgstr "" "混元最新 MOE 架構 FunctionCall 模型,經過高質量的 FunctionCall 數據訓練,上下" "文窗口達 32K,在多個維度的評測指標上處於領先。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69 msgid "" "Hunyuan's latest code generation model, after training the base model with " "200B high-quality code data, and iterating on high-quality SFT data for half " "a year, the context long window length has been increased to 8K, and it " "ranks among the top in the automatic evaluation indicators of code " "generation in the five major languages; the five major languages In the " "manual high-quality evaluation of 10 comprehensive code tasks that consider " "all aspects, the performance is in the first echelon." msgstr "" "混元最新代碼生成模型,經過 200B 高質量代碼數據增訓基座模型,迭代半年高質量 " "SFT 數據訓練,上下文長窗口長度增大到 8K,五大語言代碼生成自動評測指標上位居前" "列;五大語言10項考量各方面綜合代碼任務人工高質量評測上,性能處於第一梯隊" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77 msgid "" "Tencent's Hunyuan Embedding interface can convert text into high-quality " "vector data. The vector dimension is 1024 dimensions." msgstr "" "騰訊混元 Embedding 接口,可以將文本轉化為高質量的向量數據。向量維度為1024維。" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87 msgid "Mixed element visual model" msgstr "混元視覺模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94 msgid "Hunyuan graph model" msgstr "混元生圖模型" #: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125 msgid "Tencent Hunyuan" msgstr "騰訊混元" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24 #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42 msgid "Facebook’s 125M parameter model" msgstr "Facebook的125M參數模型" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25 msgid "BAAI’s 7B parameter model" msgstr "BAAI的7B參數模型" #: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26 msgid "BAAI’s 13B parameter mode" msgstr "BAAI的13B參數模型" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16 msgid "" "If the gap between width, height and 512 is too large, the picture rendering " "effect will be poor and the probability of excessive delay will increase " "significantly. Recommended ratio and corresponding width and height before " "super score: width*height" msgstr "" "寬、高與512差距過大,則出圖效果不佳、延遲過長概率顯著增加。超分前建議比例及對" "應寬高:width*height" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15 #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15 msgid "timbre" msgstr "音色" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "speaking speed" msgstr "語速" #: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31 msgid "[0.2,3], the default is 1, usually one decimal place is enough" msgstr "[0.2,3],默認為1,通常保留一位小數即可" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44 #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88 msgid "" "The user goes to the model inference page of Volcano Ark to create an " "inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call " "it." msgstr "" "用戶前往火山方舟的模型推理頁面創建推理接入點,這裡需要輸入ep-xxxxxxxxxx-yyyy" "進行調用" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59 msgid "Universal 2.0-Vincent Diagram" msgstr "通用2.0-文生圖" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64 msgid "Universal 2.0Pro-Vincent Chart" msgstr "通用2.0Pro-文生圖" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69 msgid "Universal 1.4-Vincent Chart" msgstr "通用1.4-文生圖" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74 msgid "Animation 1.3.0-Vincent Picture" msgstr "動漫1.3.0-文生圖" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79 msgid "Animation 1.3.1-Vincent Picture" msgstr "動漫1.3.1-文生圖" #: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113 msgid "volcano engine" msgstr "火山引擎" #: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51 #, python-brace-format msgid "{model_name} The model does not support" msgstr "{model_name} 模型不支持" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24 #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53 msgid "" "ERNIE-Bot-4 is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" "ERNIE-Bot-4是百度自行研發的大語言模型,覆蓋海量中文數據,具有更強的對話問答、" "內容創作生成等能力。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27 msgid "" "ERNIE-Bot is a large language model independently developed by Baidu. It " "covers massive Chinese data and has stronger capabilities in dialogue Q&A, " "content creation and generation." msgstr "" "ERNIE-Bot是百度自行研發的大語言模型,覆蓋海量中文數據,具有更強的對話問答、內" "容創作生成等能力。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30 msgid "" "ERNIE-Bot-turbo is a large language model independently developed by Baidu. " "It covers massive Chinese data, has stronger capabilities in dialogue Q&A, " "content creation and generation, and has a faster response speed." msgstr "" "ERNIE-Bot-turbo是百度自行研發的大語言模型,覆蓋海量中文數據,具有更強的對話問" "答、內容創作生成等能力,響應速度更快。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33 msgid "" "BLOOMZ-7B is a well-known large language model in the industry. It was " "developed and open sourced by BigScience and can output text in 46 languages " "and 13 programming languages." msgstr "" "BLOOMZ-7B是業內知名的大語言模型,由BigScience研發並開源,能夠以46種語言和13種" "程式語言輸出文本。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39 msgid "" "Llama-2-13b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning and knowledge application. " "Llama-2-13b-chat is a native open source version with balanced performance " "and effect, suitable for conversation scenarios." msgstr "" "Llama-2-13b-chat由Meta AI研發並開源,在編碼、推理及知識智能體等場景表現優秀," "Llama-2-13b-chat是性能與效果均衡的原生開源版本,適用於對話場景。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42 msgid "" "Llama-2-70b-chat was developed by Meta AI and is open source. It performs " "well in scenarios such as coding, reasoning, and knowledge application. " "Llama-2-70b-chat is a native open source version with high-precision effects." msgstr "" "Llama-2-70b-chat由Meta AI研發並開源,在編碼、推理及知識智能體等場景表現優秀," "Llama-2-70b-chat是高精度效果的原生開源版本。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45 msgid "" "The Chinese enhanced version developed by the Qianfan team based on " "Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-" "EVAL." msgstr "" "千帆團隊在Llama-2-7b基礎上的中文增強版本,在CMMLU、C-EVAL等中文知識庫上表現優" "異。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49 msgid "" "Embedding-V1 is a text representation model based on Baidu Wenxin large " "model technology. It can convert text into a vector form represented by " "numerical values and can be used in text retrieval, information " "recommendation, knowledge mining and other scenarios. Embedding-V1 provides " "the Embeddings interface, which can generate corresponding vector " "representations based on input content. You can call this interface to input " "text into the model and obtain the corresponding vector representation for " "subsequent text processing and analysis." msgstr "" "Embedding-V1是一個基於百度文心大模型技術的文本表示模型,可以將文本轉化為用數" "值表示的向量形式,用於文本檢索、信息推薦、知識挖掘等場景。 Embedding-V1提供了" "Embeddings接口,可以根據輸入內容生成對應的向量表示。您可以通過調用該接口,將" "文本輸入到模型中,獲取到對應的向量表示,從而進行後續的文本處理和分析。" #: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66 msgid "Thousand sails large model" msgstr "千帆大模型" #: apps/models_provider/impl/xf_model_provider/credential/image.py:42 msgid "Please outline this picture" msgstr "請描述這張圖片" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:15 msgid "Speaker" msgstr "發音人" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:16 msgid "" "Speaker, optional value: Please go to the console to add a trial or purchase " "speaker. After adding, the speaker parameter value will be displayed." msgstr "" "發音人,可選值:請到控制臺添加試用或購買發音人,添加後即顯示發音人參數值" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:21 msgid "iFlytek Xiaoyan" msgstr "訊飛小燕" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:22 msgid "iFlytek Xujiu" msgstr "訊飛許久" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:23 msgid "iFlytek Xiaoping" msgstr "訊飛小萍" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:24 msgid "iFlytek Xiaojing" msgstr "訊飛小婧" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:25 msgid "iFlytek Xuxiaobao" msgstr "訊飛許小寶" #: apps/models_provider/impl/xf_model_provider/credential/tts.py:28 msgid "Speech speed, optional value: [0-100], default is 50" msgstr "語速,可選值:[0-100],默認為50" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39 #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50 msgid "Chinese and English recognition" msgstr "中英文識別" #: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66 msgid "iFlytek Spark" msgstr "訊飛星火" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15 msgid "" "The image generation endpoint allows you to create raw images based on text " "prompts. The dimensions of the image can be 1024x1024, 1024x1792, or " "1792x1024 pixels." msgstr "" "圖像生成端點允許您根據文本提示創建原始圖像。圖像的尺寸可以為 1024x1024、" "1024x1792 或 1792x1024 像素。" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29 msgid "" "By default, images are generated in standard quality, you can set quality: " "\"hd\" to enhance detail. Square, standard quality images are generated " "fastest." msgstr "" "默認情況下,圖像以標準質量生成,您可以設置質量:「hd」以增強細節。方形、標準質" "量的圖像生成速度最快。" #: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42 msgid "" "You can request 1 image at a time (requesting more images by making parallel " "requests), or up to 10 images at a time using the n parameter." msgstr "" "您可以一次請求 1 個圖像(通過發出並行請求來請求更多圖像),或者使用 n 參數一" "次最多請求 10 個圖像。" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20 msgid "Chinese female" msgstr "中文女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21 msgid "Chinese male" msgstr "中文男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22 msgid "Japanese male" msgstr "日語男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23 msgid "Cantonese female" msgstr "粵語女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24 msgid "English female" msgstr "英文女" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25 msgid "English male" msgstr "英文男" #: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26 msgid "Korean female" msgstr "韓語女" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37 msgid "" "Code Llama is a language model specifically designed for code generation." msgstr "Code Llama 是一個專門用於代碼生成的語言模型。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44 msgid "" " \n" "Code Llama Instruct is a fine-tuned version of Code Llama's instructions, " "designed to perform specific tasks.\n" " " msgstr "" "Code Llama Instruct 是 Code Llama 的指令微調版本,專為執行特定任務而設計。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53 msgid "" "Code Llama Python is a language model specifically designed for Python code " "generation." msgstr "Code Llama Python 是一個專門用於 Python 代碼生成的語言模型。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60 msgid "" "CodeQwen 1.5 is a language model for code generation with high performance." msgstr "CodeQwen 1.5 是一個用於代碼生成的語言模型,具有較高的性能。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67 msgid "CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5." msgstr "CodeQwen 1.5 Chat 是一個聊天模型版本的 CodeQwen 1.5。" #: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74 msgid "Deepseek is a large-scale language model with 13 billion parameters." msgstr "Deepseek Chat 是一個聊天模型版本的 Deepseek。" #: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16 msgid "" "Image size, only cogview-3-plus supports this parameter. Optional range: " "[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the " "default is 1024x1024." msgstr "" "圖片尺寸,僅 cogview-3-plus 支持該參數。可選範圍:" "[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440],默認是" "1024x1024。" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34 msgid "" "Have strong multi-modal understanding capabilities. Able to understand up to " "five images simultaneously and supports video content understanding" msgstr "具有強大的多模態理解能力。能夠同時理解多達五張圖像,並支持視頻內容理解" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis" msgstr "專注於單圖理解。適用於需要高效圖像解析的場景" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40 msgid "" "Focus on single picture understanding. Suitable for scenarios requiring " "efficient image analysis (free)" msgstr "專注於單圖理解。適用於需要高效圖像解析的場景(免費)" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46 msgid "" "Quickly and accurately generate images based on user text descriptions. " "Resolution supports 1024x1024" msgstr "根據用戶文字描述快速、精準生成圖像。解析度支持1024x1024" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes" msgstr "根據用戶文字描述生成高質量圖像,支持多圖片尺寸" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52 msgid "" "Generate high-quality images based on user text descriptions, supporting " "multiple image sizes (free)" msgstr "根據用戶文字描述生成高質量圖像,支持多圖片尺寸(免費)" #: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75 msgid "zhipu AI" msgstr "智譜 AI" #: apps/models_provider/serializers/model_apply_serializers.py:32 #: apps/models_provider/serializers/model_apply_serializers.py:37 msgid "vector text" msgstr "向量文本" #: apps/models_provider/serializers/model_apply_serializers.py:33 msgid "vector text list" msgstr "向量文本列表" #: apps/models_provider/serializers/model_apply_serializers.py:41 msgid "text" msgstr "文本" #: apps/models_provider/serializers/model_apply_serializers.py:42 msgid "metadata" msgstr "元數據" #: apps/models_provider/serializers/model_apply_serializers.py:47 msgid "query" msgstr "查詢" #: apps/models_provider/serializers/model_serializer.py:44 #: apps/models_provider/serializers/model_serializer.py:257 msgid "parameter configuration" msgstr "參數配置" #: apps/models_provider/serializers/model_serializer.py:45 #: apps/models_provider/serializers/model_serializer.py:222 #: apps/models_provider/serializers/model_serializer.py:258 msgid "certification information" msgstr "認證信息" #: apps/models_provider/serializers/model_serializer.py:118 msgid "Shared models cannot be deleted or modified" msgstr "共享模型不能被刪除或修改" #: apps/models_provider/serializers/model_serializer.py:230 #: apps/models_provider/serializers/model_serializer.py:269 #, python-brace-format msgid "base model【{model_name}】already exists" msgstr "模型【{model_name}】已存在" #: apps/models_provider/serializers/model_serializer.py:309 msgid "Model saving failed" msgstr "模型保存失敗" #: apps/models_provider/views/model.py:60 #: apps/models_provider/views/model.py:61 #: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55 #: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57 msgid "Create model" msgstr "創建模型" #: apps/models_provider/views/model.py:90 #: apps/models_provider/views/model.py:91 #: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84 #: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86 msgid "Query model list" msgstr "查詢模型列表" #: apps/models_provider/views/model.py:107 #: apps/models_provider/views/model.py:108 #: apps/models_provider/views/model.py:109 #: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102 #: apps/shared/views/shared_model.py:103 msgid "Update model" msgstr "更新模型" #: apps/models_provider/views/model.py:125 #: apps/models_provider/views/model.py:126 #: apps/models_provider/views/model.py:127 #: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120 #: apps/shared/views/shared_model.py:121 msgid "Delete model" msgstr "刪除模型" #: apps/models_provider/views/model.py:140 #: apps/models_provider/views/model.py:141 #: apps/models_provider/views/model.py:142 #: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134 #: apps/shared/views/shared_model.py:135 msgid "Query model details" msgstr "查詢模型詳情" #: apps/models_provider/views/model.py:155 #: apps/models_provider/views/model.py:156 #: apps/models_provider/views/model.py:157 #: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149 #: apps/shared/views/shared_model.py:150 msgid "Get model parameter form" msgstr "獲取模型參數表單" #: apps/models_provider/views/model.py:167 #: apps/models_provider/views/model.py:168 #: apps/models_provider/views/model.py:169 #: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161 #: apps/shared/views/shared_model.py:162 msgid "Save model parameter form" msgstr "保存模型參數表單" #: apps/models_provider/views/model.py:187 #: apps/models_provider/views/model.py:189 #: apps/models_provider/views/model.py:191 #: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181 #: apps/shared/views/shared_model.py:183 msgid "" "Query model meta information, this interface does not carry authentication " "information" msgstr "查詢模型元信息,該接口不攜帶認證信息" #: apps/models_provider/views/model.py:204 #: apps/models_provider/views/model.py:205 #: apps/models_provider/views/model.py:206 #: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197 #: apps/shared/views/shared_model.py:198 msgid "Pause model download" msgstr "下載模型暫停" #: apps/models_provider/views/model.py:222 #: apps/models_provider/views/model.py:223 #: apps/models_provider/views/model.py:224 msgid "Get Share model" msgstr "獲取共享模型" #: apps/models_provider/views/model_apply.py:25 #: apps/models_provider/views/model_apply.py:26 #: apps/models_provider/views/model_apply.py:27 #: apps/models_provider/views/model_apply.py:37 #: apps/models_provider/views/model_apply.py:38 #: apps/models_provider/views/model_apply.py:39 msgid "Vectorization documentation" msgstr "向量化文檔" #: apps/models_provider/views/model_apply.py:49 #: apps/models_provider/views/model_apply.py:50 #: apps/models_provider/views/model_apply.py:51 msgid "Reorder documents" msgstr "重新排序文檔" #: apps/models_provider/views/provide.py:21 #: apps/models_provider/views/provide.py:22 #: apps/models_provider/views/provide.py:23 msgid "Get a list of model suppliers" msgstr "獲取模型供應商列表" #: apps/models_provider/views/provide.py:43 #: apps/models_provider/views/provide.py:44 #: apps/models_provider/views/provide.py:45 msgid "Get a list of model types" msgstr "獲取模型類型列表" #: apps/models_provider/views/provide.py:57 #: apps/models_provider/views/provide.py:58 #: apps/models_provider/views/provide.py:59 msgid "Example of obtaining model list" msgstr "獲取模型列表示例" #: apps/models_provider/views/provide.py:75 #: apps/models_provider/views/provide.py:76 #: apps/models_provider/views/provide.py:77 msgid "Get model default parameters" msgstr "獲取模型默認參數" #: apps/models_provider/views/provide.py:92 #: apps/models_provider/views/provide.py:93 #: apps/models_provider/views/provide.py:94 msgid "Get the model creation form" msgstr "獲取模型創建表單" #: apps/oss/serializers/file.py:80 msgid "File not found" msgstr "文件未找到" #: apps/oss/views/file.py:21 apps/oss/views/file.py:22 #: apps/oss/views/file.py:23 msgid "Upload file" msgstr "上傳文件" #: apps/oss/views/file.py:27 apps/oss/views/file.py:41 #: apps/oss/views/file.py:53 msgid "File" msgstr "文件" #: apps/oss/views/file.py:36 apps/oss/views/file.py:37 #: apps/oss/views/file.py:38 msgid "Get file" msgstr "獲取文件" #: apps/oss/views/file.py:48 apps/oss/views/file.py:49 #: apps/oss/views/file.py:50 msgid "Delete file" msgstr "刪除文件" #: apps/resource_manage/views/document.py:30 #: apps/resource_manage/views/document.py:31 #: apps/resource_manage/views/document.py:32 msgid "Create system knowledge" msgstr "創建系統知識庫" #: apps/resource_manage/views/document.py:36 #: apps/resource_manage/views/document.py:56 #: apps/resource_manage/views/document.py:83 #: apps/resource_manage/views/document.py:113 #: apps/resource_manage/views/document.py:130 #: apps/resource_manage/views/document.py:155 #: apps/resource_manage/views/document.py:183 #: apps/resource_manage/views/document.py:210 #: apps/resource_manage/views/document.py:237 #: apps/resource_manage/views/document.py:267 #: apps/resource_manage/views/document.py:296 #: apps/resource_manage/views/document.py:317 #: apps/resource_manage/views/document.py:345 #: apps/resource_manage/views/document.py:362 #: apps/resource_manage/views/document.py:383 #: apps/resource_manage/views/document.py:411 #: apps/resource_manage/views/document.py:435 #: apps/resource_manage/views/document.py:460 #: apps/resource_manage/views/document.py:485 #: apps/resource_manage/views/document.py:507 #: apps/resource_manage/views/document.py:530 #: apps/resource_manage/views/document.py:553 #: apps/resource_manage/views/document.py:571 #: apps/resource_manage/views/document.py:585 msgid "System Knowledge/Documentation" msgstr "系統知識庫/文檔" #: apps/resource_manage/views/document.py:51 #: apps/resource_manage/views/document.py:52 #: apps/resource_manage/views/document.py:53 msgid "Get system document" msgstr "獲取文檔" #: apps/resource_manage/views/document.py:77 #: apps/resource_manage/views/document.py:78 #: apps/resource_manage/views/document.py:79 msgid "Segmented system document" msgstr "分段文檔" #: apps/resource_manage/views/document.py:108 #: apps/resource_manage/views/document.py:109 #: apps/resource_manage/views/document.py:110 msgid "Get a list of system segment IDs" msgstr "獲取分段ID列表" #: apps/resource_manage/views/document.py:124 #: apps/resource_manage/views/document.py:125 #: apps/resource_manage/views/document.py:126 msgid "Cancel system tasks in batches" msgstr "批量取消任務" #: apps/resource_manage/views/document.py:149 #: apps/resource_manage/views/document.py:150 #: apps/resource_manage/views/document.py:151 msgid "Create system knowledges in batches" msgstr "批量創建知識庫" #: apps/resource_manage/views/document.py:177 #: apps/resource_manage/views/document.py:178 #: apps/resource_manage/views/document.py:179 msgid "Batch sync system knowledges" msgstr "批量同步知識庫" #: apps/resource_manage/views/document.py:204 #: apps/resource_manage/views/document.py:206 msgid "Delete system document in batches" msgstr "批量刪除文檔" #: apps/resource_manage/views/document.py:205 msgid "Delete system knowledge in batches" msgstr "批量刪除知識庫" #: apps/resource_manage/views/document.py:232 #: apps/resource_manage/views/document.py:233 msgid "Batch refresh system document vector library" msgstr "批量刷新文檔向量庫" #: apps/resource_manage/views/document.py:261 #: apps/resource_manage/views/document.py:262 #: apps/resource_manage/views/document.py:263 msgid "Batch generate related system problems" msgstr "批量生成相關問題" #: apps/resource_manage/views/document.py:290 #: apps/resource_manage/views/document.py:291 #: apps/resource_manage/views/document.py:292 msgid "Modify system document hit processing methods in batches" msgstr "批量修改文檔命中處理方式" #: apps/resource_manage/views/document.py:312 #: apps/resource_manage/views/document.py:313 msgid "Migrate system knowledges in batches" msgstr "批量遷移知識庫" #: apps/resource_manage/views/document.py:340 #: apps/resource_manage/views/document.py:341 #: apps/resource_manage/views/document.py:342 msgid "Get system document details" msgstr "獲取文檔詳情" #: apps/resource_manage/views/document.py:356 #: apps/resource_manage/views/document.py:357 #: apps/resource_manage/views/document.py:358 msgid "Modify system document" msgstr "修改文檔" #: apps/resource_manage/views/document.py:378 #: apps/resource_manage/views/document.py:379 #: apps/resource_manage/views/document.py:380 msgid "Delete system document" msgstr "刪除文檔" #: apps/resource_manage/views/document.py:405 #: apps/resource_manage/views/document.py:406 #: apps/resource_manage/views/document.py:407 msgid "Synchronize system web site types" msgstr "同步網站類型" #: apps/resource_manage/views/document.py:429 #: apps/resource_manage/views/document.py:430 #: apps/resource_manage/views/document.py:431 msgid "Refresh system knowledge vector library" msgstr "刷新文檔向量庫" #: apps/resource_manage/views/document.py:454 #: apps/resource_manage/views/document.py:455 #: apps/resource_manage/views/document.py:456 msgid "Cancel system task" msgstr "取消任務" #: apps/resource_manage/views/document.py:480 #: apps/resource_manage/views/document.py:481 #: apps/resource_manage/views/document.py:482 msgid "Get system document by pagination" msgstr "分頁獲取文檔" #: apps/resource_manage/views/document.py:503 #: apps/resource_manage/views/document.py:504 msgid "Export system knowledge" msgstr "導出知識庫" #: apps/resource_manage/views/document.py:526 #: apps/resource_manage/views/document.py:527 msgid "Export Zip system knowledge" msgstr "導出Zip知識庫" #: apps/resource_manage/views/document.py:549 #: apps/resource_manage/views/document.py:550 msgid "Download system source file" msgstr "下載系統源文件" #: apps/resource_manage/views/document.py:567 #: apps/resource_manage/views/document.py:568 msgid "Get system QA template" msgstr "獲取系統問答模板" #: apps/resource_manage/views/document.py:581 #: apps/resource_manage/views/document.py:582 msgid "Get system form template" msgstr "獲取系統表單模板" #: apps/resource_manage/views/knowledge.py:26 #: apps/resource_manage/views/knowledge.py:27 #: apps/resource_manage/views/knowledge.py:28 msgid "Get system knowledge list" msgstr "獲取系統知識庫列表" #: apps/resource_manage/views/knowledge.py:31 #: apps/resource_manage/views/knowledge.py:50 #: apps/resource_manage/views/knowledge.py:72 #: apps/resource_manage/views/knowledge.py:87 #: apps/resource_manage/views/knowledge.py:102 #: apps/resource_manage/views/knowledge.py:121 #: apps/resource_manage/views/knowledge.py:147 #: apps/resource_manage/views/knowledge.py:174 #: apps/resource_manage/views/knowledge.py:192 #: apps/resource_manage/views/knowledge.py:210 #: apps/resource_manage/views/knowledge.py:231 #: apps/resource_manage/views/knowledge.py:252 #: apps/resource_manage/views/knowledge.py:272 msgid "System Knowledge" msgstr "系統知識庫" #: apps/resource_manage/views/knowledge.py:45 #: apps/resource_manage/views/knowledge.py:46 #: apps/resource_manage/views/knowledge.py:47 msgid "Get system knowledge list by pagination" msgstr "獲取系統知識庫分頁列表" #: apps/resource_manage/views/knowledge.py:66 #: apps/resource_manage/views/knowledge.py:67 #: apps/resource_manage/views/knowledge.py:68 msgid "Update system knowledge" msgstr "更新系統知識庫" #: apps/resource_manage/views/knowledge.py:82 #: apps/resource_manage/views/knowledge.py:83 #: apps/resource_manage/views/knowledge.py:84 msgid "Get system knowledge" msgstr "獲取知識庫" #: apps/resource_manage/views/knowledge.py:97 #: apps/resource_manage/views/knowledge.py:98 #: apps/resource_manage/views/knowledge.py:99 msgid "Delete system knowledge" msgstr "刪除知識庫" #: apps/resource_manage/views/knowledge.py:115 #: apps/resource_manage/views/knowledge.py:116 #: apps/resource_manage/views/knowledge.py:117 msgid "Synchronize the system knowledge base of the website" msgstr "同步網站知識庫" #: apps/resource_manage/views/knowledge.py:141 #: apps/resource_manage/views/knowledge.py:142 #: apps/resource_manage/views/knowledge.py:143 msgid "System Hit test list" msgstr "命中測試列表" #: apps/resource_manage/views/knowledge.py:168 #: apps/resource_manage/views/knowledge.py:169 #: apps/resource_manage/views/knowledge.py:170 msgid "System Re-vectorize" msgstr "重新向量化" #: apps/resource_manage/views/knowledge.py:188 #: apps/resource_manage/views/knowledge.py:189 msgid "Export system knowledge base" msgstr "導出系統知識庫" #: apps/resource_manage/views/knowledge.py:206 #: apps/resource_manage/views/knowledge.py:207 msgid "Export system knowledge base containing images" msgstr "導出包含圖片的系統知識庫" #: apps/resource_manage/views/knowledge.py:225 #: apps/resource_manage/views/knowledge.py:226 #: apps/resource_manage/views/knowledge.py:227 msgid "System generate related" msgstr "生成相關" #: apps/resource_manage/views/knowledge.py:247 #: apps/resource_manage/views/knowledge.py:248 #: apps/resource_manage/views/knowledge.py:249 msgid "Get model for system knowledge base" msgstr "獲取系統知識庫模型" #: apps/resource_manage/views/knowledge.py:267 #: apps/resource_manage/views/knowledge.py:268 #: apps/resource_manage/views/knowledge.py:269 msgid "Get embedding model for system knowledge base" msgstr "獲取系統知識庫嵌入模型" #: apps/resource_manage/views/paragraph.py:24 #: apps/resource_manage/views/paragraph.py:25 #: apps/resource_manage/views/paragraph.py:26 msgid "System paragraph list" msgstr "段落列表" #: apps/resource_manage/views/paragraph.py:29 #: apps/resource_manage/views/paragraph.py:50 #: apps/resource_manage/views/paragraph.py:76 #: apps/resource_manage/views/paragraph.py:93 #: apps/resource_manage/views/paragraph.py:125 #: apps/resource_manage/views/paragraph.py:151 #: apps/resource_manage/views/paragraph.py:179 #: apps/resource_manage/views/paragraph.py:201 #: apps/resource_manage/views/paragraph.py:233 #: apps/resource_manage/views/paragraph.py:259 #: apps/resource_manage/views/paragraph.py:283 #: apps/resource_manage/views/paragraph.py:314 #: apps/resource_manage/views/paragraph.py:344 #: apps/resource_manage/views/paragraph.py:370 msgid "System Knowledge/Documentation/Paragraph" msgstr "系統知識庫/文檔/段落" #: apps/resource_manage/views/paragraph.py:45 #: apps/resource_manage/views/paragraph.py:46 msgid "Create system paragraph" msgstr "創建段落" #: apps/resource_manage/views/paragraph.py:70 #: apps/resource_manage/views/paragraph.py:71 #: apps/resource_manage/views/paragraph.py:72 msgid "Batch system paragraph" msgstr "批量關聯段落" #: apps/resource_manage/views/paragraph.py:88 #: apps/resource_manage/views/paragraph.py:89 msgid "Migrate system paragraphs in batches" msgstr "批量遷移段落" #: apps/resource_manage/views/paragraph.py:119 #: apps/resource_manage/views/paragraph.py:120 #: apps/resource_manage/views/paragraph.py:121 msgid "Batch generate system related" msgstr "批量生成相關" #: apps/resource_manage/views/paragraph.py:145 #: apps/resource_manage/views/paragraph.py:146 #: apps/resource_manage/views/paragraph.py:147 msgid "Modify system paragraph data" msgstr "修改段落數據" #: apps/resource_manage/views/paragraph.py:174 #: apps/resource_manage/views/paragraph.py:175 #: apps/resource_manage/views/paragraph.py:176 msgid "Get system paragraph details" msgstr "獲取段落詳情" #: apps/resource_manage/views/paragraph.py:196 #: apps/resource_manage/views/paragraph.py:197 #: apps/resource_manage/views/paragraph.py:198 msgid "Delete system paragraph" msgstr "刪除段落" #: apps/resource_manage/views/paragraph.py:227 #: apps/resource_manage/views/paragraph.py:228 #: apps/resource_manage/views/paragraph.py:229 msgid "Add system associated questions" msgstr "添加關聯問題" #: apps/resource_manage/views/paragraph.py:254 #: apps/resource_manage/views/paragraph.py:255 #: apps/resource_manage/views/paragraph.py:256 msgid "Get a list of system paragraph questions" msgstr "獲取段落問題列表" #: apps/resource_manage/views/paragraph.py:277 #: apps/resource_manage/views/paragraph.py:278 #: apps/resource_manage/views/paragraph.py:279 msgid "Disassociation system issue" msgstr "取消關聯問題" #: apps/resource_manage/views/paragraph.py:308 #: apps/resource_manage/views/paragraph.py:309 #: apps/resource_manage/views/paragraph.py:310 msgid "Related system questions" msgstr "關聯問題" #: apps/resource_manage/views/paragraph.py:339 #: apps/resource_manage/views/paragraph.py:340 #: apps/resource_manage/views/paragraph.py:341 msgid "Get system paragraph list by pagination" msgstr "獲取段落列表" #: apps/resource_manage/views/problem.py:23 #: apps/resource_manage/views/problem.py:24 #: apps/resource_manage/views/problem.py:25 msgid "System question list" msgstr "問題列表" #: apps/resource_manage/views/problem.py:28 #: apps/resource_manage/views/problem.py:50 #: apps/resource_manage/views/problem.py:71 #: apps/resource_manage/views/problem.py:94 #: apps/resource_manage/views/problem.py:115 #: apps/resource_manage/views/problem.py:135 #: apps/resource_manage/views/problem.py:158 #: apps/resource_manage/views/problem.py:182 msgid "System Knowledge/Documentation/Paragraph/Question" msgstr "系統知識庫/文檔/段落/問題" #: apps/resource_manage/views/problem.py:44 #: apps/resource_manage/views/problem.py:45 #: apps/resource_manage/views/problem.py:46 msgid "Create system question" msgstr "創建問題" #: apps/resource_manage/views/problem.py:66 #: apps/resource_manage/views/problem.py:67 #: apps/resource_manage/views/problem.py:68 msgid "Get a list of associated system paragraphs" msgstr "獲取關聯段落列表" #: apps/resource_manage/views/problem.py:88 #: apps/resource_manage/views/problem.py:89 #: apps/resource_manage/views/problem.py:90 msgid "Batch associated system paragraphs" msgstr "批量關聯段落" #: apps/resource_manage/views/problem.py:109 #: apps/resource_manage/views/problem.py:110 #: apps/resource_manage/views/problem.py:111 msgid "Batch deletion system issues" msgstr "批量刪除問題" #: apps/resource_manage/views/problem.py:130 #: apps/resource_manage/views/problem.py:131 #: apps/resource_manage/views/problem.py:132 msgid "Delete system question" msgstr "刪除問題" #: apps/resource_manage/views/problem.py:152 #: apps/resource_manage/views/problem.py:153 #: apps/resource_manage/views/problem.py:154 msgid "Modify system question" msgstr "修改問題" #: apps/resource_manage/views/problem.py:177 #: apps/resource_manage/views/problem.py:178 #: apps/resource_manage/views/problem.py:179 msgid "Get the list of system questions by page" msgstr "分頁獲取問題列表" #: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25 #: apps/resource_manage/views/tool.py:26 msgid "Get system tool" msgstr "獲取工具" #: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50 #: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80 #: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119 #: apps/resource_manage/views/tool.py:137 #: apps/resource_manage/views/tool.py:156 #: apps/resource_manage/views/tool.py:179 msgid "System Tool" msgstr "工具" #: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45 #: apps/resource_manage/views/tool.py:46 msgid "Update system tool" msgstr "更新工具" #: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61 #: apps/resource_manage/views/tool.py:62 msgid "Get system tool by id" msgstr "獲取工具" #: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76 #: apps/resource_manage/views/tool.py:77 msgid "Delete system tool" msgstr "刪除工具" #: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94 #: apps/resource_manage/views/tool.py:95 msgid "Get system tool list by pagination" msgstr "獲取工具列表" #: apps/resource_manage/views/tool.py:114 #: apps/resource_manage/views/tool.py:115 #: apps/resource_manage/views/tool.py:116 msgid "Export system tool" msgstr "導出工具" #: apps/resource_manage/views/tool.py:132 #: apps/resource_manage/views/tool.py:133 #: apps/resource_manage/views/tool.py:134 msgid "Debug system tool" msgstr "調試工具" #: apps/resource_manage/views/tool.py:150 #: apps/resource_manage/views/tool.py:151 #: apps/resource_manage/views/tool.py:152 msgid "Check system code" msgstr "檢查代碼" #: apps/resource_manage/views/tool.py:173 #: apps/resource_manage/views/tool.py:174 #: apps/resource_manage/views/tool.py:175 msgid "Edit system tool icon" msgstr "修改工具圖標" #: apps/role_setting/api/role_setting.py:16 #: apps/role_setting/api/role_setting.py:22 #: apps/role_setting/api/role_setting.py:33 #: apps/role_setting/api/role_setting.py:143 #: apps/role_setting/serializers/role_setting_serializers.py:193 #: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104 msgid "ID" msgstr "" #: apps/role_setting/api/role_setting.py:17 #: apps/role_setting/api/role_setting.py:23 #: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258 #: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235 msgid "Name" msgstr "用戶名" #: apps/role_setting/api/role_setting.py:18 #: apps/role_setting/api/role_setting.py:29 #: apps/role_setting/serializers/role_setting_serializers.py:194 msgid "Enable" msgstr "啟用" #: apps/role_setting/api/role_setting.py:26 msgid "Permission" msgstr "權限" #: apps/role_setting/api/role_setting.py:37 msgid "Children" msgstr "子級" #: apps/role_setting/api/role_setting.py:55 #: apps/role_setting/serializers/role_setting_serializers.py:107 msgid "Role type" msgstr "角色類型" #: apps/role_setting/api/role_setting.py:76 msgid "Internal role" msgstr "內置角色" #: apps/role_setting/api/role_setting.py:80 msgid "Custom role" msgstr "自定義角色" #: apps/role_setting/api/role_setting.py:108 #: apps/role_setting/api/role_setting.py:128 #: apps/role_setting/api/role_setting.py:164 #: apps/role_setting/serializers/role_setting_serializers.py:110 #: apps/role_setting/serializers/role_setting_serializers.py:329 #: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86 msgid "Role ID" msgstr "角色 ID" #: apps/role_setting/api/role_setting.py:135 msgid "User relation ID" msgstr "用戶關係 ID" #: apps/role_setting/api/role_setting.py:145 #: apps/role_setting/api/role_setting.py:185 #: apps/role_setting/serializers/role_setting_serializers.py:330 #: apps/users/api/user.py:77 apps/users/serializers/login.py:27 #: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114 #: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124 #: apps/workspace/serializers/workspace_serializers.py:240 #: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105 #: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62 #: apps/xpack/serializers/chat_user.py:564 msgid "Username" msgstr "用戶名" #: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84 #: apps/xpack/api/chat_user.py:106 msgid "Nickname" msgstr "暱稱" #: apps/role_setting/api/role_setting.py:148 msgid "Workspace Name" msgstr "工作空間" #: apps/role_setting/serializers/role_setting_serializers.py:104 msgid "Role name" msgstr "角色名稱" #: apps/role_setting/serializers/role_setting_serializers.py:122 #: apps/role_setting/serializers/role_setting_serializers.py:200 #: apps/role_setting/serializers/role_setting_serializers.py:325 #: apps/role_setting/serializers/role_setting_serializers.py:337 msgid "Role does not exist" msgstr "角色不存在" #: apps/role_setting/serializers/role_setting_serializers.py:124 #: apps/role_setting/serializers/role_setting_serializers.py:202 msgid "Cannot modify built-in role" msgstr "不能修改內置角色" #: apps/role_setting/serializers/role_setting_serializers.py:132 msgid "Role name already exists" msgstr "角色名稱已存在" #: apps/role_setting/serializers/role_setting_serializers.py:152 msgid "Invalid role type" msgstr "無效的角色類型" #: apps/role_setting/serializers/role_setting_serializers.py:204 msgid "Cannot delete built-in role" msgstr "無法刪除內置角色" #: apps/role_setting/serializers/role_setting_serializers.py:262 #: apps/users/api/user.py:135 apps/users/serializers/user.py:471 #: apps/workspace/serializers/workspace_serializers.py:161 #: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158 #: apps/xpack/serializers/chat_user.py:172 #: apps/xpack/serializers/chat_user.py:502 msgid "User IDs" msgstr "用戶 ID" #: apps/role_setting/serializers/role_setting_serializers.py:267 #: apps/users/api/user.py:30 msgid "Workspace IDs" msgstr "工作空間 ID" #: apps/role_setting/serializers/role_setting_serializers.py:272 #: apps/workspace/serializers/workspace_serializers.py:172 msgid "Members" msgstr "成員集合" #: apps/role_setting/serializers/role_setting_serializers.py:312 #: apps/workspace/serializers/workspace_serializers.py:223 msgid "User relation does not exist" msgstr "用戶關係不存在" #: apps/role_setting/serializers/role_setting_serializers.py:316 #: apps/workspace/serializers/workspace_serializers.py:226 msgid "Cannot remove member from built-in role" msgstr "不能從內置角色中移除成員" #: apps/role_setting/serializers/role_setting_serializers.py:370 msgid "Only update members to normal users" msgstr "只能為普通用戶更新成員" #: apps/role_setting/views/role_setting.py:39 #: apps/role_setting/views/role_setting.py:40 #: apps/role_setting/views/role_setting.py:41 msgid "Get role permission template" msgstr "獲取角色權限模板" #: apps/role_setting/views/role_setting.py:62 #: apps/role_setting/views/role_setting.py:63 #: apps/role_setting/views/role_setting.py:64 msgid "Create or update role" msgstr "創建或更新角色" #: apps/role_setting/views/role_setting.py:80 #: apps/role_setting/views/role_setting.py:81 #: apps/role_setting/views/role_setting.py:82 msgid "Get role list" msgstr "獲取角色列表" #: apps/role_setting/views/role_setting.py:98 #: apps/role_setting/views/role_setting.py:99 #: apps/role_setting/views/role_setting.py:100 msgid "Delete role" msgstr "刪除角色" #: apps/role_setting/views/role_setting.py:120 #: apps/role_setting/views/role_setting.py:121 #: apps/role_setting/views/role_setting.py:122 msgid "Create or update role permission" msgstr "創建或更新角色權限" #: apps/role_setting/views/role_setting.py:140 #: apps/role_setting/views/role_setting.py:141 #: apps/role_setting/views/role_setting.py:142 msgid "Get role permission" msgstr "獲取角色權限" #: apps/role_setting/views/role_setting.py:161 #: apps/role_setting/views/role_setting.py:162 #: apps/role_setting/views/role_setting.py:163 msgid "Add member to system role" msgstr "系統角色添加成員" #: apps/role_setting/views/role_setting.py:186 #: apps/role_setting/views/role_setting.py:187 #: apps/role_setting/views/role_setting.py:188 msgid "Remove member from system role" msgstr "系統角色移除成員" #: apps/role_setting/views/role_setting.py:205 #: apps/role_setting/views/role_setting.py:206 #: apps/role_setting/views/role_setting.py:207 msgid "Get system role member list" msgstr "獲取系統角色成員列表" #: apps/role_setting/views/role_setting.py:223 #: apps/role_setting/views/role_setting.py:224 #: apps/role_setting/views/role_setting.py:225 msgid "Get Workspace role list" msgstr "獲取工作空間角色列表" #: apps/role_setting/views/role_setting.py:227 #: apps/role_setting/views/role_setting.py:248 #: apps/role_setting/views/role_setting.py:273 #: apps/role_setting/views/role_setting.py:292 msgid "Workspace Role" msgstr "工作空間角色" #: apps/role_setting/views/role_setting.py:242 #: apps/role_setting/views/role_setting.py:243 #: apps/role_setting/views/role_setting.py:244 msgid "Add member to workspace role" msgstr "工作空間角色添加成員" #: apps/role_setting/views/role_setting.py:268 #: apps/role_setting/views/role_setting.py:269 #: apps/role_setting/views/role_setting.py:270 msgid "Remove member from workspace role" msgstr "工作空間角色移除成員" #: apps/role_setting/views/role_setting.py:287 #: apps/role_setting/views/role_setting.py:288 #: apps/role_setting/views/role_setting.py:289 msgid "Get workspace role member list" msgstr "獲取工作空間角色成員列表" #: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54 msgid "Folder token" msgstr "文件夾 Token" #: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46 #: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133 #: apps/shared/serializers/shared_tool.py:43 #: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142 #: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454 msgid "tool name" msgstr "工具名稱" #: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53 #: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140 #: apps/shared/serializers/shared_tool.py:44 #: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144 #: apps/tools/serializers/tool.py:159 msgid "tool description" msgstr "工具描述" #: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184 #: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288 #: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66 #: apps/shared/serializers/shared_tool.py:107 #: apps/tools/serializers/tool.py:267 msgid "tool id" msgstr "工具 ID" #: apps/shared/serializers/shared_knowledge.py:35 #: apps/shared/serializers/shared_model.py:20 #: apps/shared/serializers/shared_tool.py:19 msgid "workspace id list" msgstr "工作空間ID" #: apps/shared/serializers/shared_knowledge.py:36 #: apps/shared/serializers/shared_model.py:21 #: apps/shared/serializers/shared_tool.py:20 msgid "authentication type" msgstr "認證類型" #: apps/shared/serializers/shared_knowledge.py:196 #: apps/shared/serializers/shared_knowledge.py:216 #: apps/shared/serializers/shared_knowledge.py:236 msgid "Knowledge does not exist" msgstr "知識庫不存在" #: apps/shared/serializers/shared_knowledge.py:218 #: apps/shared/serializers/shared_knowledge.py:238 msgid "Only shared knowledge can be authorized" msgstr "僅限共享知識庫可以被授權" #: apps/shared/serializers/shared_tool.py:74 msgid "Only shared tools can be deleted" msgstr "僅限共享工具可以被刪除" #: apps/shared/serializers/shared_tool.py:76 msgid "System tools cannot be deleted" msgstr "系統工具不能被刪除" #: apps/shared/serializers/shared_tool.py:118 #: apps/shared/serializers/shared_tool.py:138 msgid "Tool does not exist" msgstr "工具不存在" #: apps/shared/serializers/shared_tool.py:120 #: apps/shared/serializers/shared_tool.py:140 msgid "Only shared tools can be authorized" msgstr "僅限共享工具可以被授權" #: apps/shared/views/shared_dataset_lark_views.py:25 #: apps/shared/views/shared_dataset_lark_views.py:26 #: apps/shared/views/shared_dataset_lark_views.py:27 #: apps/xpack/views/dataset_lark_views.py:23 #: apps/xpack/views/dataset_lark_views.py:24 #: apps/xpack/views/dataset_lark_views.py:25 msgid "Create a lark knowledge base" msgstr "創建知識庫" #: apps/shared/views/shared_dataset_lark_views.py:44 #: apps/shared/views/shared_dataset_lark_views.py:45 #: apps/shared/views/shared_dataset_lark_views.py:46 #: apps/xpack/views/dataset_lark_views.py:44 #: apps/xpack/views/dataset_lark_views.py:45 #: apps/xpack/views/dataset_lark_views.py:46 msgid "Update a lark knowledge base" msgstr "更新飛書知識庫" #: apps/shared/views/shared_dataset_lark_views.py:67 #: apps/shared/views/shared_dataset_lark_views.py:68 #: apps/shared/views/shared_dataset_lark_views.py:69 #: apps/xpack/views/dataset_lark_views.py:67 #: apps/xpack/views/dataset_lark_views.py:68 #: apps/xpack/views/dataset_lark_views.py:69 msgid "Get document list from lark" msgstr "獲取飛書文檔列表" #: apps/shared/views/shared_dataset_lark_views.py:72 #: apps/shared/views/shared_dataset_lark_views.py:90 #: apps/shared/views/shared_dataset_lark_views.py:109 #: apps/shared/views/shared_dataset_lark_views.py:129 #: apps/shared/views/shared_document.py:36 #: apps/shared/views/shared_document.py:56 #: apps/shared/views/shared_document.py:83 #: apps/shared/views/shared_document.py:114 #: apps/shared/views/shared_document.py:131 #: apps/shared/views/shared_document.py:156 #: apps/shared/views/shared_document.py:184 #: apps/shared/views/shared_document.py:211 #: apps/shared/views/shared_document.py:238 #: apps/shared/views/shared_document.py:268 #: apps/shared/views/shared_document.py:297 #: apps/shared/views/shared_document.py:318 #: apps/shared/views/shared_document.py:346 #: apps/shared/views/shared_document.py:363 #: apps/shared/views/shared_document.py:384 #: apps/shared/views/shared_document.py:412 #: apps/shared/views/shared_document.py:436 #: apps/shared/views/shared_document.py:461 #: apps/shared/views/shared_document.py:486 #: apps/shared/views/shared_document.py:508 #: apps/shared/views/shared_document.py:531 #: apps/shared/views/shared_document.py:554 #: apps/shared/views/shared_document.py:575 #: apps/shared/views/shared_document.py:602 #: apps/shared/views/shared_document.py:629 #: apps/shared/views/shared_document.py:651 #: apps/shared/views/shared_document.py:665 msgid "Shared Knowledge/Documentation" msgstr "共享知識庫/文檔" #: apps/shared/views/shared_dataset_lark_views.py:85 #: apps/shared/views/shared_dataset_lark_views.py:86 #: apps/shared/views/shared_dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:86 #: apps/xpack/views/dataset_lark_views.py:87 #: apps/xpack/views/dataset_lark_views.py:88 msgid "Import documents to the lark knowledge base" msgstr "導入文檔到飛書知識庫" #: apps/shared/views/shared_dataset_lark_views.py:104 #: apps/shared/views/shared_dataset_lark_views.py:105 #: apps/shared/views/shared_dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:106 #: apps/xpack/views/dataset_lark_views.py:107 #: apps/xpack/views/dataset_lark_views.py:108 msgid "Synchronize lark document" msgstr "同步飛書文檔" #: apps/shared/views/shared_dataset_lark_views.py:123 #: apps/shared/views/shared_dataset_lark_views.py:124 #: apps/shared/views/shared_dataset_lark_views.py:125 #: apps/xpack/views/dataset_lark_views.py:126 #: apps/xpack/views/dataset_lark_views.py:127 #: apps/xpack/views/dataset_lark_views.py:128 msgid "Batch synchronize lark document" msgstr "批量同步飛書文檔" #: apps/shared/views/shared_document.py:30 #: apps/shared/views/shared_document.py:31 #: apps/shared/views/shared_document.py:32 msgid "Create shared document" msgstr "創建共享文檔" #: apps/shared/views/shared_document.py:51 #: apps/shared/views/shared_document.py:52 #: apps/shared/views/shared_document.py:53 msgid "Get shared document" msgstr "獲取共享文檔" #: apps/shared/views/shared_document.py:77 #: apps/shared/views/shared_document.py:78 #: apps/shared/views/shared_document.py:79 msgid "Segmented shared document" msgstr "分段共享文檔" #: apps/shared/views/shared_document.py:109 #: apps/shared/views/shared_document.py:110 #: apps/shared/views/shared_document.py:111 msgid "Get a list of shared segment IDs" msgstr "獲取共享分段 ID 列表" #: apps/shared/views/shared_document.py:125 #: apps/shared/views/shared_document.py:126 #: apps/shared/views/shared_document.py:127 msgid "Cancel shared tasks in batches" msgstr "批量取消任務" #: apps/shared/views/shared_document.py:150 #: apps/shared/views/shared_document.py:151 #: apps/shared/views/shared_document.py:152 msgid "Create shared documents in batches" msgstr "批量創建共享文檔" #: apps/shared/views/shared_document.py:178 #: apps/shared/views/shared_document.py:179 #: apps/shared/views/shared_document.py:180 msgid "Batch sync shared documents" msgstr "批量同步共享文檔" #: apps/shared/views/shared_document.py:205 #: apps/shared/views/shared_document.py:206 #: apps/shared/views/shared_document.py:207 msgid "Delete shared documents in batches" msgstr "批量刪除共享文檔" #: apps/shared/views/shared_document.py:233 #: apps/shared/views/shared_document.py:234 msgid "Batch refresh shared document vector library" msgstr "批量刷新共享文檔向量庫" #: apps/shared/views/shared_document.py:262 #: apps/shared/views/shared_document.py:263 #: apps/shared/views/shared_document.py:264 msgid "Batch generate related shared problems" msgstr "批量生成相關問題" #: apps/shared/views/shared_document.py:291 #: apps/shared/views/shared_document.py:292 #: apps/shared/views/shared_document.py:293 msgid "Modify shared document hit processing methods in batches" msgstr "批量修改文檔命中處理方法" #: apps/shared/views/shared_document.py:313 #: apps/shared/views/shared_document.py:314 msgid "Migrate shared documents in batches" msgstr "批量遷移共享文檔" #: apps/shared/views/shared_document.py:341 #: apps/shared/views/shared_document.py:342 #: apps/shared/views/shared_document.py:343 msgid "Get shared document details" msgstr "文檔文檔詳情" #: apps/shared/views/shared_document.py:357 #: apps/shared/views/shared_document.py:358 #: apps/shared/views/shared_document.py:359 msgid "Modify shared document" msgstr "修改共享文檔" #: apps/shared/views/shared_document.py:379 #: apps/shared/views/shared_document.py:380 #: apps/shared/views/shared_document.py:381 msgid "Delete shared document" msgstr "刪除共享文檔" #: apps/shared/views/shared_document.py:406 #: apps/shared/views/shared_document.py:407 #: apps/shared/views/shared_document.py:408 msgid "Synchronize shared web site types" msgstr "同步共享網站類型" #: apps/shared/views/shared_document.py:430 #: apps/shared/views/shared_document.py:431 #: apps/shared/views/shared_document.py:432 msgid "Refresh shared document vector library" msgstr "刷新共享文檔向量庫" #: apps/shared/views/shared_document.py:455 #: apps/shared/views/shared_document.py:456 #: apps/shared/views/shared_document.py:457 msgid "Cancel shared task" msgstr "取消任務" #: apps/shared/views/shared_document.py:481 #: apps/shared/views/shared_document.py:482 #: apps/shared/views/shared_document.py:483 msgid "Get shared document by pagination" msgstr "獲取共享文檔分頁列表" #: apps/shared/views/shared_document.py:504 #: apps/shared/views/shared_document.py:505 msgid "Export shared document" msgstr "導出共享文檔" #: apps/shared/views/shared_document.py:527 #: apps/shared/views/shared_document.py:528 msgid "Export Zip shared document" msgstr "導出共享文檔 Zip" #: apps/shared/views/shared_document.py:550 #: apps/shared/views/shared_document.py:551 msgid "Download shared source file" msgstr "下載共享源文件" #: apps/shared/views/shared_document.py:569 #: apps/shared/views/shared_document.py:571 msgid "Create Web site shared documents" msgstr "創建網站文檔" #: apps/shared/views/shared_document.py:596 #: apps/shared/views/shared_document.py:597 #: apps/shared/views/shared_document.py:598 msgid "Import QA and create shared documentation" msgstr "導入問答並創建文檔" #: apps/shared/views/shared_document.py:623 #: apps/shared/views/shared_document.py:624 #: apps/shared/views/shared_document.py:625 msgid "Import tables and create shared documents" msgstr "導入表格並創建文檔" #: apps/shared/views/shared_document.py:647 #: apps/shared/views/shared_document.py:648 msgid "Get shared QA template" msgstr "獲取共享問答模板" #: apps/shared/views/shared_document.py:661 #: apps/shared/views/shared_document.py:662 msgid "Get shared form template" msgstr "獲取共享表格模板" #: apps/shared/views/shared_knowledge.py:28 #: apps/shared/views/shared_knowledge.py:29 #: apps/shared/views/shared_knowledge.py:30 msgid "Get share resource knowledge" msgstr "獲取共享資源知識庫" #: apps/shared/views/shared_knowledge.py:48 #: apps/shared/views/shared_knowledge.py:49 #: apps/shared/views/shared_knowledge.py:50 msgid "Get shared knowledge list by pagination" msgstr "獲取共享知識庫分頁列表" #: apps/shared/views/shared_knowledge.py:70 #: apps/shared/views/shared_knowledge.py:71 #: apps/shared/views/shared_knowledge.py:72 msgid "Update shared knowledge" msgstr "更新共享知識庫" #: apps/shared/views/shared_knowledge.py:86 #: apps/shared/views/shared_knowledge.py:87 #: apps/shared/views/shared_knowledge.py:88 msgid "Get shared knowledge" msgstr "獲取共享知識庫" #: apps/shared/views/shared_knowledge.py:101 #: apps/shared/views/shared_knowledge.py:102 #: apps/shared/views/shared_knowledge.py:103 msgid "Delete shared knowledge" msgstr "刪除共享知識庫" #: apps/shared/views/shared_knowledge.py:119 #: apps/shared/views/shared_knowledge.py:120 #: apps/shared/views/shared_knowledge.py:121 msgid "Synchronize the shared knowledge base of the website" msgstr "同步共享知識庫網站" #: apps/shared/views/shared_knowledge.py:145 #: apps/shared/views/shared_knowledge.py:146 #: apps/shared/views/shared_knowledge.py:147 msgid "Shared Hit test list" msgstr "命中測試列表" #: apps/shared/views/shared_knowledge.py:172 #: apps/shared/views/shared_knowledge.py:173 #: apps/shared/views/shared_knowledge.py:174 msgid "Shared Re-vectorize" msgstr "重新向量化" #: apps/shared/views/shared_knowledge.py:192 #: apps/shared/views/shared_knowledge.py:193 msgid "Export shared knowledge base" msgstr "導出共享知識庫" #: apps/shared/views/shared_knowledge.py:210 #: apps/shared/views/shared_knowledge.py:211 msgid "Export shared knowledge base containing images" msgstr "導出包含圖片的共享知識庫" #: apps/shared/views/shared_knowledge.py:229 #: apps/shared/views/shared_knowledge.py:230 #: apps/shared/views/shared_knowledge.py:231 msgid "Shared generate related" msgstr "生成相關" #: apps/shared/views/shared_knowledge.py:251 #: apps/shared/views/shared_knowledge.py:252 #: apps/shared/views/shared_knowledge.py:253 msgid "Get model for shared knowledge base" msgstr "獲取共享知識庫模型" #: apps/shared/views/shared_knowledge.py:271 #: apps/shared/views/shared_knowledge.py:272 #: apps/shared/views/shared_knowledge.py:273 msgid "Get embedding model for shared knowledge base" msgstr "獲取共享知識庫嵌入模型" #: apps/shared/views/shared_knowledge.py:291 #: apps/shared/views/shared_knowledge.py:293 msgid "Authorization knowledge workspace" msgstr "授權知識工作空間" #: apps/shared/views/shared_knowledge.py:292 msgid "Authorization knowledge workspace " msgstr "授權知識工作空間" #: apps/shared/views/shared_knowledge.py:307 #: apps/shared/views/shared_knowledge.py:308 #: apps/shared/views/shared_knowledge.py:309 msgid "Get Authorization knowledge workspace" msgstr "獲取授權知識工作空間" #: apps/shared/views/shared_knowledge.py:326 #: apps/shared/views/shared_knowledge.py:327 #: apps/shared/views/shared_knowledge.py:328 msgid "Create shared base knowledge" msgstr "創建共享知識庫" #: apps/shared/views/shared_knowledge.py:349 #: apps/shared/views/shared_knowledge.py:350 #: apps/shared/views/shared_knowledge.py:351 msgid "Create shared web knowledge" msgstr "創建 web 知識庫" #: apps/shared/views/shared_knowledge.py:381 #: apps/shared/views/shared_knowledge.py:382 #: apps/shared/views/shared_knowledge.py:383 msgid "Get shared workspace knowledge" msgstr "獲取共享工作空間知識庫" #: apps/shared/views/shared_knowledge.py:402 #: apps/shared/views/shared_knowledge.py:403 #: apps/shared/views/shared_knowledge.py:404 msgid "Get shared workspace knowledge list by pagination" msgstr "獲取共享工作空間知識庫分頁列表" #: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215 msgid "Authorization model workspace" msgstr "授權模型工作空間" #: apps/shared/views/shared_model.py:214 msgid "Authorization model workspace " msgstr "授權模型工作空間" #: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230 #: apps/shared/views/shared_model.py:231 msgid "Get Authorization model workspace" msgstr "獲取授權模型工作空間" #: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249 #: apps/shared/views/shared_model.py:250 msgid "Get Share model by workspace id" msgstr "獲取共享模型工作空間 ID" #: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266 #: apps/shared/views/shared_model.py:267 msgid "Get Share model by workspace id with pagination" msgstr "獲取共享模型工作空間 ID 分頁列表" #: apps/shared/views/shared_paragraph.py:24 #: apps/shared/views/shared_paragraph.py:25 #: apps/shared/views/shared_paragraph.py:26 msgid "Shared paragraph list" msgstr "共享段落列表" #: apps/shared/views/shared_paragraph.py:29 #: apps/shared/views/shared_paragraph.py:50 #: apps/shared/views/shared_paragraph.py:76 #: apps/shared/views/shared_paragraph.py:93 #: apps/shared/views/shared_paragraph.py:125 #: apps/shared/views/shared_paragraph.py:151 #: apps/shared/views/shared_paragraph.py:179 #: apps/shared/views/shared_paragraph.py:201 #: apps/shared/views/shared_paragraph.py:233 #: apps/shared/views/shared_paragraph.py:259 #: apps/shared/views/shared_paragraph.py:283 #: apps/shared/views/shared_paragraph.py:314 #: apps/shared/views/shared_paragraph.py:345 #: apps/shared/views/shared_paragraph.py:371 msgid "Shared Knowledge/Documentation/Paragraph" msgstr "共享知識庫/文檔/段落" #: apps/shared/views/shared_paragraph.py:45 #: apps/shared/views/shared_paragraph.py:46 msgid "Create shared paragraph" msgstr "創建段落" #: apps/shared/views/shared_paragraph.py:70 #: apps/shared/views/shared_paragraph.py:71 #: apps/shared/views/shared_paragraph.py:72 msgid "Batch shared paragraph" msgstr "批量關聯段落" #: apps/shared/views/shared_paragraph.py:88 #: apps/shared/views/shared_paragraph.py:89 msgid "Migrate shared paragraphs in batches" msgstr "批量遷移共享段落" #: apps/shared/views/shared_paragraph.py:119 #: apps/shared/views/shared_paragraph.py:120 #: apps/shared/views/shared_paragraph.py:121 msgid "Batch generate shared related" msgstr "批量生成相關" #: apps/shared/views/shared_paragraph.py:145 #: apps/shared/views/shared_paragraph.py:146 #: apps/shared/views/shared_paragraph.py:147 msgid "Modify shared paragraph data" msgstr "修改段落數據" #: apps/shared/views/shared_paragraph.py:174 #: apps/shared/views/shared_paragraph.py:175 #: apps/shared/views/shared_paragraph.py:176 msgid "Get shared paragraph details" msgstr "獲取段落詳情" #: apps/shared/views/shared_paragraph.py:196 #: apps/shared/views/shared_paragraph.py:197 #: apps/shared/views/shared_paragraph.py:198 msgid "Delete shared paragraph" msgstr "刪除段落" #: apps/shared/views/shared_paragraph.py:227 #: apps/shared/views/shared_paragraph.py:228 #: apps/shared/views/shared_paragraph.py:229 msgid "Add shared associated questions" msgstr "添加關聯問題" #: apps/shared/views/shared_paragraph.py:254 #: apps/shared/views/shared_paragraph.py:255 #: apps/shared/views/shared_paragraph.py:256 msgid "Get a list of shared paragraph questions" msgstr "獲取共享段落問題列表" #: apps/shared/views/shared_paragraph.py:277 #: apps/shared/views/shared_paragraph.py:278 #: apps/shared/views/shared_paragraph.py:279 msgid "Disassociation shared issue" msgstr "取消關聯問題" #: apps/shared/views/shared_paragraph.py:308 #: apps/shared/views/shared_paragraph.py:309 #: apps/shared/views/shared_paragraph.py:310 msgid "Related shared questions" msgstr "關聯問題" #: apps/shared/views/shared_paragraph.py:340 #: apps/shared/views/shared_paragraph.py:341 #: apps/shared/views/shared_paragraph.py:342 msgid "Get shared paragraph list by pagination" msgstr "獲取段落列表" #: apps/shared/views/shared_problem.py:23 #: apps/shared/views/shared_problem.py:24 #: apps/shared/views/shared_problem.py:25 msgid "Shared question list" msgstr "問題列表" #: apps/shared/views/shared_problem.py:28 #: apps/shared/views/shared_problem.py:50 #: apps/shared/views/shared_problem.py:71 #: apps/shared/views/shared_problem.py:94 #: apps/shared/views/shared_problem.py:115 #: apps/shared/views/shared_problem.py:135 #: apps/shared/views/shared_problem.py:158 #: apps/shared/views/shared_problem.py:182 msgid "Shared Knowledge/Documentation/Paragraph/Question" msgstr "知識庫/文檔/段落/問題" #: apps/shared/views/shared_problem.py:44 #: apps/shared/views/shared_problem.py:45 #: apps/shared/views/shared_problem.py:46 msgid "Create shared question" msgstr "創建問題" #: apps/shared/views/shared_problem.py:66 #: apps/shared/views/shared_problem.py:67 #: apps/shared/views/shared_problem.py:68 msgid "Get a list of associated shared paragraphs" msgstr "獲取關聯段落列表" #: apps/shared/views/shared_problem.py:88 #: apps/shared/views/shared_problem.py:89 #: apps/shared/views/shared_problem.py:90 msgid "Batch associated shared paragraphs" msgstr "批量關聯段落" #: apps/shared/views/shared_problem.py:109 #: apps/shared/views/shared_problem.py:110 #: apps/shared/views/shared_problem.py:111 msgid "Batch deletion shared issues" msgstr "批量刪除問題" #: apps/shared/views/shared_problem.py:130 #: apps/shared/views/shared_problem.py:131 #: apps/shared/views/shared_problem.py:132 msgid "Delete shared question" msgstr "刪除問題" #: apps/shared/views/shared_problem.py:152 #: apps/shared/views/shared_problem.py:153 #: apps/shared/views/shared_problem.py:154 msgid "Modify shared question" msgstr "修改問題" #: apps/shared/views/shared_problem.py:177 #: apps/shared/views/shared_problem.py:178 #: apps/shared/views/shared_problem.py:179 msgid "Get the list of shared questions by page" msgstr "分頁獲取問題列表" #: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26 #: apps/shared/views/shared_tool.py:27 msgid "Get share resource tool" msgstr "獲取共享資源工具" #: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44 #: apps/shared/views/shared_tool.py:45 msgid "Create shared tool" msgstr "創建共享工具" #: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63 #: apps/shared/views/shared_tool.py:64 msgid "Update shared tool" msgstr "更新共享工具" #: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79 #: apps/shared/views/shared_tool.py:80 msgid "Get shared tool" msgstr "獲取共享工具" #: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94 #: apps/shared/views/shared_tool.py:95 msgid "Delete shared tool" msgstr "刪除共享工具" #: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112 #: apps/shared/views/shared_tool.py:113 msgid "Get shared tool list by pagination" msgstr "獲取共享工具列表" #: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135 #: apps/shared/views/shared_tool.py:136 msgid "Import shared tool" msgstr "導入共享工具" #: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153 #: apps/shared/views/shared_tool.py:154 msgid "Export shared tool" msgstr "導出共享工具" #: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171 #: apps/shared/views/shared_tool.py:172 msgid "Debug shared Tool" msgstr "調試共享工具" #: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189 #: apps/shared/views/shared_tool.py:190 msgid "Check shared code" msgstr "檢查代碼" #: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213 #: apps/shared/views/shared_tool.py:214 msgid "Edit shared tool icon" msgstr "修改共享工具圖標" #: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235 msgid "Authorization tool workspace" msgstr "授權工作空間工具" #: apps/shared/views/shared_tool.py:234 msgid "Authorization tool workspace " msgstr "授權工作空間工具" #: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250 #: apps/shared/views/shared_tool.py:251 msgid "Get Authorization tool workspace" msgstr "獲取授權工作空間工具" #: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269 #: apps/shared/views/shared_tool.py:270 msgid "Get shared workspace tool" msgstr "獲取共享工作空間工具" #: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290 #: apps/shared/views/shared_tool.py:291 msgid "Get shared workspace tool list by pagination" msgstr "獲取共享工作空間工具分頁列表" #: apps/system_manage/serializers/email_setting.py:28 msgid "SMTP host" msgstr "" #: apps/system_manage/serializers/email_setting.py:29 msgid "SMTP port" msgstr "" #: apps/system_manage/serializers/email_setting.py:30 #: apps/system_manage/serializers/email_setting.py:34 msgid "Sender's email" msgstr "發送者郵箱" #: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93 #: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57 #: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291 #: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25 #: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24 #: apps/xpack/serializers/chat_user.py:74 #: apps/xpack/serializers/chat_user.py:267 #: apps/xpack/serializers/chat_user.py:601 msgid "Password" msgstr "密碼" #: apps/system_manage/serializers/email_setting.py:32 msgid "Whether to enable TLS" msgstr "是否啟用 TLS" #: apps/system_manage/serializers/email_setting.py:33 msgid "Whether to enable SSL" msgstr "是否啟用 SSL" #: apps/system_manage/serializers/email_setting.py:49 msgid "Email verification failed" msgstr "郵件認證失敗" #: apps/system_manage/serializers/user_resource_permission.py:53 msgid "target id" msgstr "當前 ID" #: apps/system_manage/serializers/user_resource_permission.py:70 msgid "Non-existent application|knowledge base id[" msgstr "不存在的智能體|知識庫 ID[" #: apps/system_manage/views/email_setting.py:50 #: apps/system_manage/views/email_setting.py:51 #: apps/system_manage/views/email_setting.py:52 msgid "Create or update email settings" msgstr "創建或更新郵件設置" #: apps/system_manage/views/email_setting.py:55 #: apps/system_manage/views/email_setting.py:70 #: apps/system_manage/views/email_setting.py:86 msgid "Email Settings" msgstr "郵箱設置" #: apps/system_manage/views/email_setting.py:66 #: apps/system_manage/views/email_setting.py:67 msgid "Test email settings" msgstr "測試郵箱設置" #: apps/system_manage/views/email_setting.py:82 #: apps/system_manage/views/email_setting.py:83 #: apps/system_manage/views/email_setting.py:84 msgid "Get email settings" msgstr "獲取郵箱設置" #: apps/system_manage/views/system_profile.py:22 #: apps/system_manage/views/system_profile.py:23 msgid "Get MaxKB related information" msgstr "獲取 MaxKB 相關信息" #: apps/system_manage/views/system_profile.py:25 msgid "System parameters" msgstr "系統參數" #: apps/system_manage/views/user_resource_permission.py:40 #: apps/system_manage/views/user_resource_permission.py:41 msgid "Obtain resource authorization list" msgstr "獲取資源授權列表" #: apps/system_manage/views/user_resource_permission.py:44 #: apps/system_manage/views/user_resource_permission.py:60 msgid "Resources authorization" msgstr "資源授權" #: apps/system_manage/views/user_resource_permission.py:55 #: apps/system_manage/views/user_resource_permission.py:56 msgid "Modify the resource authorization list" msgstr "修改資源授權列表" #: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169 msgid "variable name" msgstr "變量名稱" #: apps/tools/serializers/tool.py:122 msgid "fields only support string|int|dict|array|float" msgstr "欄位僅支持字符串|整數|字典|數組|浮點數" #: apps/tools/serializers/tool.py:131 msgid "field name" msgstr "欄位名稱" #: apps/tools/serializers/tool.py:132 msgid "field label" msgstr "標籤" #: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160 #: apps/tools/serializers/tool.py:174 msgid "tool content" msgstr "工具內容" #: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161 #: apps/tools/serializers/tool.py:175 msgid "input field list" msgstr "輸入欄位列表" #: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162 #: apps/tools/serializers/tool.py:176 msgid "init field list" msgstr "內置欄位列表" #: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177 msgid "init params" msgstr "內置參數" #: apps/tools/serializers/tool.py:170 msgid "variable value" msgstr "變量名稱" #: apps/tools/serializers/tool.py:182 msgid "function content" msgstr "工具內容" #: apps/tools/serializers/tool.py:238 msgid "field has no value set" msgstr "欄位未設置值" #: apps/tools/serializers/tool.py:262 #, python-brace-format msgid "Field: {name} Type: {_type} Value: {value} Type conversion error" msgstr "欄位:{name} 類型:{_type} 值:{value} 類型轉換錯誤" #: apps/tools/serializers/tool.py:275 msgid "Tool not found" msgstr "工具不存在" #: apps/tools/serializers/tool.py:388 msgid "function ID" msgstr "工具 ID" #: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34 #: apps/tools/views/tool.py:35 msgid "Create tool" msgstr "創建工具" #: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57 #: apps/tools/views/tool.py:58 msgid "Get tool by folder" msgstr "通過文件夾獲取工具" #: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78 #: apps/tools/views/tool.py:79 msgid "Debug Tool" msgstr "調試工具" #: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99 #: apps/tools/views/tool.py:100 msgid "Update tool" msgstr "更新工具" #: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123 #: apps/tools/views/tool.py:124 msgid "Get tool" msgstr "獲取工具" #: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142 #: apps/tools/views/tool.py:143 msgid "Delete tool" msgstr "刪除工具" #: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168 #: apps/tools/views/tool.py:169 msgid "Get tool list by pagination" msgstr "獲取工具列表" #: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196 #: apps/tools/views/tool.py:197 msgid "Import tool" msgstr "導入工具" #: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219 #: apps/tools/views/tool.py:220 msgid "Export tool" msgstr "導出工具" #: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245 #: apps/tools/views/tool.py:246 msgid "Check code" msgstr "檢查代碼" #: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269 #: apps/tools/views/tool.py:270 msgid "Edit tool icon" msgstr "修改工具圖標" #: apps/users/api/user.py:154 msgid "Email or Username" msgstr "郵箱或用戶名" #: apps/users/api/user.py:224 msgid "Language" msgstr "語言" #: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69 msgid "captcha" msgstr "驗證碼" #: apps/users/serializers/login.py:50 #: apps/xpack/serializers/chat_user_serializer.py:120 msgid "Captcha code error or expiration" msgstr "驗證碼錯誤或過期" #: apps/users/serializers/login.py:55 #: apps/xpack/serializers/auth_config_serializer.py:192 #: apps/xpack/serializers/chat_user_serializer.py:125 #: apps/xpack/serializers/qr_login/qr_login.py:37 msgid "The user has been disabled, please contact the administrator!" msgstr "用戶已被禁用,請聯繫管理員!" #: apps/users/serializers/user.py:47 msgid "Is Edit Password" msgstr "是否編輯密碼" #: apps/users/serializers/user.py:48 msgid "permissions" msgstr "無權限訪問" #: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106 #: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557 #: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107 #: apps/xpack/serializers/chat_user.py:54 #: apps/xpack/serializers/chat_user.py:227 msgid "Email" msgstr "郵箱" #: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140 #: apps/xpack/serializers/chat_user.py:88 msgid "Nick name" msgstr "暱稱" #: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145 #: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108 #: apps/xpack/serializers/chat_user.py:93 #: apps/xpack/serializers/chat_user.py:240 msgid "Phone" msgstr "手機" #: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68 msgid "Username must be 4-64 characters long" msgstr "用戶名必須為4-64個字符" #: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298 #: apps/xpack/serializers/chat_user.py:81 #: apps/xpack/serializers/chat_user.py:274 msgid "" "The password must be 6-20 characters long and must be a combination of " "letters, numbers, and special characters." msgstr "密碼必須為6-20個字符,且必須包含大小写字母、數字和特殊字符。" #: apps/users/serializers/user.py:170 msgid "Email or username" msgstr "郵箱或用戶名" #: apps/users/serializers/user.py:226 msgid "" "The community version supports up to 2 users. If you need more users, please " "contact us (https://fit2cloud.com/)." msgstr "" "社區版支持最多2個用戶,如需更多用戶,請聯繫我們(https://fit2cloud.com/)。" #: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31 #: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247 msgid "Is Active" msgstr "是否啟用" #: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262 msgid "Nickname is already in use" msgstr "Nickname已被使用" #: apps/users/serializers/user.py:286 msgid "Email is already in use" msgstr "郵箱已被使用" #: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281 msgid "Re Password" msgstr "確認密碼" #: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522 #: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286 #: apps/xpack/serializers/chat_user.py:606 #: apps/xpack/serializers/chat_user.py:613 msgid "" "The confirmation password must be 6-20 characters long and must be a " "combination of letters, numbers, and special characters." msgstr "確認密碼必須為6-20個字符,且必須包含字母、數字和特殊字符。" #: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309 msgid "User does not exist" msgstr "用戶不存在" #: apps/users/serializers/user.py:348 msgid "Unable to delete administrator" msgstr "無法刪除管理員" #: apps/users/serializers/user.py:366 msgid "Cannot modify administrator status" msgstr "不能修改管理員狀態" #: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164 #: apps/xpack/serializers/chat_user.py:192 #: apps/xpack/serializers/chat_user.py:512 msgid "User IDs cannot be empty" msgstr "用戶 ID 不能為空" #: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608 msgid "Confirm Password" msgstr "確認密碼" #: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647 #: apps/xpack/api/knowledge_lark.py:26 msgid "Type" msgstr "類型" #: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651 msgid "The type only supports register|reset_password" msgstr "類型僅支持 register|reset_password" #: apps/users/serializers/user.py:581 #, python-brace-format msgid "Do not send emails again within {seconds} seconds" msgstr "不要在 {seconds} 秒內再次發送郵件" #: apps/users/serializers/user.py:611 msgid "" "The email service has not been set up. Please contact the administrator to " "set up the email service in [Email Settings]." msgstr "郵箱服務尚未設置,請聯繫管理員在 [郵箱設置] 中設置郵箱服務。" #: apps/users/serializers/user.py:622 #, python-brace-format msgid "【Intelligent knowledge base question and answer system-{action}】" msgstr "【智能知識庫問答系統-{action}】" #: apps/users/serializers/user.py:623 msgid "User registration" msgstr "用戶註冊" #: apps/users/serializers/user.py:623 apps/users/views/user.py:248 #: apps/users/views/user.py:249 apps/users/views/user.py:250 #: apps/users/views/user.py:283 apps/users/views/user.py:284 #: apps/users/views/user.py:285 msgid "Change password" msgstr "修改密碼" #: apps/users/serializers/user.py:644 msgid "Verification code" msgstr "驗證碼" #: apps/users/serializers/user.py:672 msgid "language only support:" msgstr "語言僅支持:" #: apps/users/views/login.py:38 apps/users/views/login.py:39 #: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184 #: apps/xpack/views/chat_user_auth.py:185 #: apps/xpack/views/chat_user_auth.py:186 msgid "Log in" msgstr "登錄" #: apps/users/views/login.py:55 apps/users/views/login.py:56 #: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203 #: apps/xpack/views/chat_user_auth.py:204 #: apps/xpack/views/chat_user_auth.py:205 msgid "Sign out" msgstr "登出" #: apps/users/views/user.py:60 apps/users/views/user.py:61 #: apps/users/views/user.py:62 apps/users/views/user.py:74 #: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359 #: apps/xpack/views/system_chat_user.py:360 #: apps/xpack/views/system_chat_user.py:361 msgid "Get current user information" msgstr "獲取當前用戶信息" #: apps/users/views/user.py:120 apps/users/views/user.py:121 #: apps/users/views/user.py:122 msgid "Get all user" msgstr "獲取所有用戶" #: apps/users/views/user.py:133 apps/users/views/user.py:134 #: apps/users/views/user.py:135 msgid "Get user list under workspace" msgstr "獲取工作空間下用戶列表" #: apps/users/views/user.py:147 apps/users/views/user.py:148 #: apps/users/views/user.py:149 msgid "Get user member under workspace" msgstr "獲取工作空間下用戶成員" #: apps/users/views/user.py:161 apps/users/views/user.py:162 #: apps/users/views/user.py:163 msgid "Create user" msgstr "創建用戶" #: apps/users/views/user.py:177 apps/users/views/user.py:178 #: apps/users/views/user.py:179 msgid "Get default password" msgstr "獲取默認密碼" #: apps/users/views/user.py:190 apps/users/views/user.py:191 #: apps/users/views/user.py:192 msgid "Delete user" msgstr "刪除用戶" #: apps/users/views/user.py:203 apps/users/views/user.py:204 #: apps/users/views/user.py:205 msgid "Get user information" msgstr "獲取用戶信息" #: apps/users/views/user.py:214 apps/users/views/user.py:215 #: apps/users/views/user.py:216 msgid "Update user information" msgstr "更新當前用戶信息" #: apps/users/views/user.py:232 apps/users/views/user.py:233 #: apps/users/views/user.py:234 msgid "Batch delete user" msgstr "批量刪除用戶" #: apps/users/views/user.py:266 apps/users/views/user.py:267 #: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168 #: apps/workspace/views/workspace_chat_user.py:169 #: apps/workspace/views/workspace_chat_user.py:170 #: apps/xpack/views/system_chat_user.py:197 #: apps/xpack/views/system_chat_user.py:198 #: apps/xpack/views/system_chat_user.py:199 msgid "Get user paginated list" msgstr "獲取用戶分頁列表" #: apps/users/views/user.py:300 apps/users/views/user.py:301 #: apps/users/views/user.py:302 msgid "Send email" msgstr "發送郵件" #: apps/users/views/user.py:318 apps/users/views/user.py:319 #: apps/users/views/user.py:320 msgid "Check whether the verification code is correct" msgstr "檢查驗證碼是否正確" #: apps/users/views/user.py:335 apps/users/views/user.py:336 #: apps/users/views/user.py:337 msgid "Send email to current user" msgstr "發送郵件給當前用戶" #: apps/users/views/user.py:353 apps/users/views/user.py:354 #: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336 #: apps/xpack/views/system_chat_user.py:337 #: apps/xpack/views/system_chat_user.py:338 msgid "Modify current user password" msgstr "修改當前用戶密碼" #: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352 msgid "Failed to change password" msgstr "修改密碼失敗" #: apps/workspace/api/workspace.py:73 #: apps/workspace/serializers/workspace_serializers.py:213 msgid "User Relation ID" msgstr "用戶關係 ID" #: apps/workspace/api/workspace.py:87 msgid "Role Name" msgstr "角色名稱" #: apps/workspace/serializers/workspace_serializers.py:42 #: apps/workspace/serializers/workspace_serializers.py:95 #: apps/workspace/serializers/workspace_serializers.py:110 #: apps/workspace/serializers/workspace_serializers.py:177 #: apps/workspace/serializers/workspace_serializers.py:219 #: apps/workspace/serializers/workspace_serializers.py:246 msgid "Workspace does not exist" msgstr "工作空間不存在" #: apps/workspace/serializers/workspace_serializers.py:49 msgid "Workspace name already exists" msgstr "工作空間名稱已存在" #: apps/workspace/serializers/workspace_serializers.py:97 #: apps/workspace/serializers/workspace_serializers.py:112 msgid "Default workspace cannot be deleted" msgstr "默認工作空間不能被刪除" #: apps/workspace/serializers/workspace_serializers.py:122 msgid "Applications Resource" msgstr "智能體資源" #: apps/workspace/serializers/workspace_serializers.py:124 msgid "Knowledge Resource" msgstr "知識庫資源" #: apps/workspace/serializers/workspace_serializers.py:130 msgid "This workspace contains %s, cannot be deleted." msgstr "此工作空間包含 %s,不能被刪除。" #: apps/workspace/serializers/workspace_serializers.py:166 msgid "Role IDs" msgstr "角色 IDs" #: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33 #: apps/workspace/views/workspace.py:34 msgid "Create or update workspace" msgstr "創建或更新工作空間" #: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46 #: apps/workspace/views/workspace.py:47 msgid "Get system workspace list" msgstr "獲取系統工作空間列表" #: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59 #: apps/workspace/views/workspace.py:60 msgid "Delete workspace" msgstr "刪除工作空間" #: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76 #: apps/workspace/views/workspace.py:77 msgid "Check workspace can it be deleted" msgstr "檢查工作空間是否可以被刪除" #: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96 #: apps/workspace/views/workspace.py:97 msgid "Add member to system workspace" msgstr "系統工作空間添加成員" #: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115 #: apps/workspace/views/workspace.py:116 msgid "Remove member from system workspace" msgstr "系統工作空間移除成員" #: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134 #: apps/workspace/views/workspace.py:135 msgid "Get system workspace member list" msgstr "獲取系統工作空間成員列表" #: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152 #: apps/workspace/views/workspace.py:153 msgid "Get workspace list" msgstr "獲取工作空間列表" #: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165 #: apps/workspace/views/workspace.py:166 msgid "Add member to workspace" msgstr "工作空間添加成員" #: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184 #: apps/workspace/views/workspace.py:185 msgid "Remove member from workspace" msgstr "工作空間移除成員" #: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203 #: apps/workspace/views/workspace.py:204 msgid "Get workspace member list" msgstr "獲取工作空間成員列表" #: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220 #: apps/workspace/views/workspace.py:221 msgid "Get workspace list by current user" msgstr "獲取當前用戶工作空間列表" #: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233 #: apps/workspace/views/workspace.py:234 msgid "Get workspace list by user" msgstr "根據用戶獲取工作空間列表" #: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247 #: apps/workspace/views/workspace.py:248 msgid "Get current user role list" msgstr "獲取當前用戶角色列表" #: apps/workspace/views/workspace_chat_user.py:44 #: apps/workspace/views/workspace_chat_user.py:45 #: apps/workspace/views/workspace_chat_user.py:46 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/xpack/views/system_chat_user.py:57 #: apps/xpack/views/system_chat_user.py:58 #: apps/xpack/views/system_chat_user.py:59 msgid "Create chat user" msgstr "創建對話用戶" #: apps/workspace/views/workspace_chat_user.py:47 #: apps/workspace/views/workspace_chat_user.py:51 #: apps/workspace/views/workspace_chat_user.py:63 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/workspace/views/workspace_chat_user.py:76 #: apps/workspace/views/workspace_chat_user.py:87 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/workspace/views/workspace_chat_user.py:105 #: apps/workspace/views/workspace_chat_user.py:120 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/workspace/views/workspace_chat_user.py:136 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/workspace/views/workspace_chat_user.py:152 #: apps/workspace/views/workspace_chat_user.py:171 msgid "Workspace/Chat user" msgstr "工作空間/對話用戶" #: apps/workspace/views/workspace_chat_user.py:60 #: apps/workspace/views/workspace_chat_user.py:61 #: apps/workspace/views/workspace_chat_user.py:62 #: apps/workspace/views/workspace_chat_user.py:67 #: apps/xpack/views/system_chat_user.py:86 #: apps/xpack/views/system_chat_user.py:87 #: apps/xpack/views/system_chat_user.py:88 msgid "Delete chat user" msgstr "刪除對話用戶" #: apps/workspace/views/workspace_chat_user.py:73 #: apps/workspace/views/workspace_chat_user.py:74 #: apps/workspace/views/workspace_chat_user.py:75 #: apps/xpack/views/system_chat_user.py:99 #: apps/xpack/views/system_chat_user.py:100 #: apps/xpack/views/system_chat_user.py:101 msgid "Get chat user information" msgstr "獲取對話用戶信息" #: apps/workspace/views/workspace_chat_user.py:84 #: apps/workspace/views/workspace_chat_user.py:85 #: apps/workspace/views/workspace_chat_user.py:86 #: apps/workspace/views/workspace_chat_user.py:92 #: apps/xpack/views/system_chat_user.py:110 #: apps/xpack/views/system_chat_user.py:111 #: apps/xpack/views/system_chat_user.py:112 msgid "Update chat user information" msgstr "更新對話用戶信息" #: apps/workspace/views/workspace_chat_user.py:102 #: apps/workspace/views/workspace_chat_user.py:103 #: apps/workspace/views/workspace_chat_user.py:104 #: apps/workspace/views/workspace_chat_user.py:261 #: apps/workspace/views/workspace_chat_user.py:262 #: apps/workspace/views/workspace_chat_user.py:263 #: apps/xpack/views/system_chat_user.py:128 #: apps/xpack/views/system_chat_user.py:129 #: apps/xpack/views/system_chat_user.py:130 #: apps/xpack/views/system_chat_user.py:318 #: apps/xpack/views/system_chat_user.py:319 #: apps/xpack/views/system_chat_user.py:320 msgid "Get user list by group" msgstr "獲取用戶組列表" #: apps/workspace/views/workspace_chat_user.py:117 #: apps/workspace/views/workspace_chat_user.py:118 #: apps/workspace/views/workspace_chat_user.py:119 #: apps/workspace/views/workspace_chat_user.py:124 #: apps/xpack/views/system_chat_user.py:143 #: apps/xpack/views/system_chat_user.py:144 #: apps/xpack/views/system_chat_user.py:145 msgid "Batch delete chat user" msgstr "批量刪除對話用戶" #: apps/workspace/views/workspace_chat_user.py:133 #: apps/workspace/views/workspace_chat_user.py:134 #: apps/workspace/views/workspace_chat_user.py:135 #: apps/workspace/views/workspace_chat_user.py:140 #: apps/xpack/views/system_chat_user.py:160 #: apps/xpack/views/system_chat_user.py:161 #: apps/xpack/views/system_chat_user.py:162 msgid "Batch add chat user to group" msgstr "批量添加對話用戶到用戶組" #: apps/workspace/views/workspace_chat_user.py:149 #: apps/workspace/views/workspace_chat_user.py:150 #: apps/workspace/views/workspace_chat_user.py:151 #: apps/xpack/views/system_chat_user.py:177 #: apps/xpack/views/system_chat_user.py:178 #: apps/xpack/views/system_chat_user.py:179 msgid "Change chat user password" msgstr "修改對話用戶密碼" #: apps/workspace/views/workspace_chat_user.py:186 #: apps/workspace/views/workspace_chat_user.py:187 #: apps/workspace/views/workspace_chat_user.py:188 #: apps/xpack/views/system_chat_user.py:230 #: apps/xpack/views/system_chat_user.py:231 #: apps/xpack/views/system_chat_user.py:232 msgid "Create or update Chat User Group" msgstr "創建或更新對話用戶組" #: apps/workspace/views/workspace_chat_user.py:191 #: apps/workspace/views/workspace_chat_user.py:202 #: apps/workspace/views/workspace_chat_user.py:216 #: apps/workspace/views/workspace_chat_user.py:232 #: apps/workspace/views/workspace_chat_user.py:249 #: apps/workspace/views/workspace_chat_user.py:264 msgid "Workspace/User Group" msgstr "工作空間/用戶組" #: apps/workspace/views/workspace_chat_user.py:198 #: apps/workspace/views/workspace_chat_user.py:199 #: apps/workspace/views/workspace_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:243 #: apps/xpack/views/system_chat_user.py:244 #: apps/xpack/views/system_chat_user.py:245 msgid "Get user group list" msgstr "獲取用戶組列表" #: apps/workspace/views/workspace_chat_user.py:211 #: apps/workspace/views/workspace_chat_user.py:212 #: apps/workspace/views/workspace_chat_user.py:213 #: apps/xpack/views/system_chat_user.py:256 #: apps/xpack/views/system_chat_user.py:257 #: apps/xpack/views/system_chat_user.py:258 msgid "Delete chat user group" msgstr "刪除對話用戶組" #: apps/workspace/views/workspace_chat_user.py:226 #: apps/workspace/views/workspace_chat_user.py:227 #: apps/workspace/views/workspace_chat_user.py:228 #: apps/xpack/views/system_chat_user.py:273 #: apps/xpack/views/system_chat_user.py:274 #: apps/xpack/views/system_chat_user.py:275 msgid "Add member to chat user group" msgstr "添加成員到對話用戶組" #: apps/workspace/views/workspace_chat_user.py:243 #: apps/workspace/views/workspace_chat_user.py:244 #: apps/workspace/views/workspace_chat_user.py:245 #: apps/xpack/views/system_chat_user.py:295 #: apps/xpack/views/system_chat_user.py:296 #: apps/xpack/views/system_chat_user.py:297 msgid "Remove member from chat user group" msgstr "從對話用戶組移除成員" #: apps/xpack/api/auth_config.py:29 msgid "Auth Type" msgstr "認證類型" #: apps/xpack/api/auth_config.py:30 msgid "Config" msgstr "配置" #: apps/xpack/api/auth_config.py:77 msgid "Corp ID" msgstr "" #: apps/xpack/api/auth_config.py:78 msgid "Agent ID" msgstr "" #: apps/xpack/api/auth_config.py:79 msgid "App Secret" msgstr "" #: apps/xpack/api/auth_config.py:80 msgid "Callback URL" msgstr "" #: apps/xpack/api/auth_config.py:84 msgid "Key" msgstr "" #: apps/xpack/api/auth_config.py:106 msgid "Access Token" msgstr "" #: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83 #: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101 #: apps/xpack/serializers/chat_user.py:177 #: apps/xpack/serializers/chat_user.py:252 msgid "User Group IDs" msgstr "用戶組 IDs" #: apps/xpack/api/chat_user.py:118 msgid "User Group Names" msgstr "用戶組名稱" #: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120 #: apps/xpack/serializers/resource_chat_user.py:37 msgid "Username or Nickname" msgstr "用戶名或暱稱" #: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360 msgid "Sync Type" msgstr "同步類型" #: apps/xpack/api/knowledge_lark.py:25 msgid "Token" msgstr "令牌" #: apps/xpack/api/knowledge_lark.py:27 msgid "Is Exist" msgstr "是否存在" #: apps/xpack/api/license.py:13 msgid "corporation" msgstr "公司" #: apps/xpack/api/license.py:14 msgid "isv" msgstr "" #: apps/xpack/api/license.py:15 msgid "expired" msgstr "過期時間" #: apps/xpack/api/license.py:16 msgid "product" msgstr "產品" #: apps/xpack/api/license.py:17 msgid "edition" msgstr "版本" #: apps/xpack/api/license.py:18 msgid "license version" msgstr "license 版本" #: apps/xpack/api/license.py:19 msgid "count" msgstr "數量" #: apps/xpack/api/license.py:20 msgid "serial number" msgstr "序列號" #: apps/xpack/api/license.py:21 msgid "remark" msgstr "備註" #: apps/xpack/api/license.py:26 msgid "message" msgstr "消息" #: apps/xpack/api/license.py:27 msgid "license details" msgstr "license 詳情" #: apps/xpack/api/license.py:36 #: apps/xpack/serializers/license/license_serializers.py:56 msgid "license file" msgstr "license 文件" #: apps/xpack/api/license.py:37 msgid "License file is required" msgstr "license 文件是必需的" #: apps/xpack/api/license.py:38 msgid "Invalid license file format" msgstr "無效的 license 文件格式" #: apps/xpack/api/operate_log.py:12 #: apps/xpack/serializers/operate_log_serializer.py:57 msgid "menu" msgstr "菜單" #: apps/xpack/api/operate_log.py:13 #: apps/xpack/serializers/operate_log_serializer.py:58 msgid "operate" msgstr "操作" #: apps/xpack/api/operate_log.py:14 msgid "menu_label" msgstr "菜單標籤" #: apps/xpack/api/operate_log.py:15 msgid "operate_label" msgstr "操作標籤" #: apps/xpack/api/platform.py:35 msgid "Platform type" msgstr "平臺類型" #: apps/xpack/api/platform.py:50 msgid "Platform configuration" msgstr "平臺配置" #: apps/xpack/api/resource_chat_user_group.py:40 #: apps/xpack/serializers/resource_chat_user.py:25 #: apps/xpack/serializers/resource_chat_user_group.py:69 msgid "is auth" msgstr "是否認證" #: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99 msgid "User Group ID" msgstr "用戶組 ID" #: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406 #: apps/xpack/serializers/chat_user.py:563 msgid "Group ID" msgstr "用戶組 ID" #: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541 msgid "User group relation IDs" msgstr "用戶組關係 ID" #: apps/xpack/serializers/application_setting_serializer.py:19 msgid "theme color" msgstr "主題顏色" #: apps/xpack/serializers/application_setting_serializer.py:21 msgid "header font color" msgstr "標題字體顏色" #: apps/xpack/serializers/application_setting_serializer.py:25 msgid "float location type" msgstr "浮動位置類型" #: apps/xpack/serializers/application_setting_serializer.py:26 msgid "float location value" msgstr "浮動位置值" #: apps/xpack/serializers/application_setting_serializer.py:30 msgid "float location x" msgstr "浮動位置 X" #: apps/xpack/serializers/application_setting_serializer.py:31 msgid "float location y" msgstr "浮動位置 Y" #: apps/xpack/serializers/application_setting_serializer.py:35 msgid "show source" msgstr "顯示來源" #: apps/xpack/serializers/application_setting_serializer.py:36 msgid "show exec" msgstr "顯示執行" #: apps/xpack/serializers/application_setting_serializer.py:38 msgid "show history" msgstr "顯示歷史" #: apps/xpack/serializers/application_setting_serializer.py:39 msgid "draggable" msgstr "是否可拖動" #: apps/xpack/serializers/application_setting_serializer.py:40 msgid "show guide" msgstr "顯示論壇" #: apps/xpack/serializers/application_setting_serializer.py:42 msgid "icon url" msgstr "圖標" #: apps/xpack/serializers/application_setting_serializer.py:43 msgid "chat background" msgstr "聊天背景" #: apps/xpack/serializers/application_setting_serializer.py:44 msgid "chat background url" msgstr "聊天背景地址" #: apps/xpack/serializers/application_setting_serializer.py:45 msgid "avatar" msgstr "頭像" #: apps/xpack/serializers/application_setting_serializer.py:46 msgid "avatar url" msgstr "頭像地址" #: apps/xpack/serializers/application_setting_serializer.py:47 msgid "user avatar" msgstr "用戶頭像" #: apps/xpack/serializers/application_setting_serializer.py:48 msgid "user avatar url" msgstr "用戶頭像地址" #: apps/xpack/serializers/application_setting_serializer.py:49 msgid "float icon" msgstr "浮動圖標" #: apps/xpack/serializers/application_setting_serializer.py:50 msgid "float icon url" msgstr "浮動圖標地址" #: apps/xpack/serializers/application_setting_serializer.py:51 msgid "disclaimer" msgstr "免責申明" #: apps/xpack/serializers/application_setting_serializer.py:52 msgid "disclaimer value" msgstr "免責申明內容" #: apps/xpack/serializers/application_setting_serializer.py:55 msgid "show avatar" msgstr "顯示頭像" #: apps/xpack/serializers/application_setting_serializer.py:56 msgid "show user avatar" msgstr "顯示用戶頭像" #: apps/xpack/serializers/application_setting_serializer.py:124 msgid "Float location field type error" msgstr "浮動位置欄位類型錯誤" #: apps/xpack/serializers/application_setting_serializer.py:130 msgid "Custom theme field type error" msgstr "自定義主題欄位類型錯誤" #: apps/xpack/serializers/auth_config_serializer.py:27 #: apps/xpack/serializers/platform_serializer.py:31 msgid "App Secret is required" msgstr "App Secret 是必填項" #: apps/xpack/serializers/auth_config_serializer.py:28 #: apps/xpack/serializers/platform_serializer.py:26 #: apps/xpack/serializers/platform_serializer.py:34 #: apps/xpack/serializers/platform_serializer.py:40 #: apps/xpack/serializers/platform_serializer.py:46 msgid "Callback URL is required" msgstr "Callback URL 是必填項" #: apps/xpack/serializers/auth_config_serializer.py:32 #: apps/xpack/serializers/auth_config_serializer.py:41 msgid "Corp ID is required" msgstr "Corp ID 是必填項" #: apps/xpack/serializers/auth_config_serializer.py:33 #: apps/xpack/serializers/platform_serializer.py:22 msgid "Agent ID is required" msgstr "Agent ID 是必填項" #: apps/xpack/serializers/auth_config_serializer.py:37 #: apps/xpack/serializers/auth_config_serializer.py:42 msgid "App Key is required" msgstr "App Key 是必填項" #: apps/xpack/serializers/auth_config_serializer.py:53 msgid "LDAP server cannot be empty" msgstr "LDAP server不能為空" #: apps/xpack/serializers/auth_config_serializer.py:54 msgid "Base DN cannot be empty" msgstr "Base DN不能為空" #: apps/xpack/serializers/auth_config_serializer.py:55 msgid "Password cannot be empty" msgstr "密碼不能為空" #: apps/xpack/serializers/auth_config_serializer.py:56 msgid "OU cannot be empty" msgstr "OU不能為空" #: apps/xpack/serializers/auth_config_serializer.py:57 msgid "LDAP filter cannot be empty" msgstr "LDAP過濾器不能為空" #: apps/xpack/serializers/auth_config_serializer.py:58 msgid "LDAP mapping cannot be empty" msgstr "LDAP映射不能為空" #: apps/xpack/serializers/auth_config_serializer.py:62 msgid "Authorization address cannot be empty" msgstr "認證地址不能為空" #: apps/xpack/serializers/auth_config_serializer.py:63 msgid "Token address cannot be empty" msgstr "令牌地址不能為空" #: apps/xpack/serializers/auth_config_serializer.py:64 msgid "User information address cannot be empty" msgstr "用戶信息地址不能為空" #: apps/xpack/serializers/auth_config_serializer.py:65 msgid "Scope cannot be empty" msgstr "範圍不能為空" #: apps/xpack/serializers/auth_config_serializer.py:66 msgid "Client ID cannot be empty" msgstr "客戶端 ID 不能為空" #: apps/xpack/serializers/auth_config_serializer.py:67 msgid "Client secret cannot be empty" msgstr "客戶端密鑰不能為空" #: apps/xpack/serializers/auth_config_serializer.py:68 msgid "Redirect address cannot be empty" msgstr "重定向地址不能為空" #: apps/xpack/serializers/auth_config_serializer.py:69 msgid "Field mapping cannot be empty" msgstr "欄位映射不能為空" #: apps/xpack/serializers/auth_config_serializer.py:262 msgid "Configuration information is wrong and failed to save" msgstr "配置信息錯誤,保存失敗" #: apps/xpack/serializers/auth_config_serializer.py:288 msgid "Connection failed" msgstr "連接失敗" #: apps/xpack/serializers/auth_config_serializer.py:306 msgid "Platform does not exist" msgstr "平臺不存在" #: apps/xpack/serializers/auth_config_serializer.py:316 msgid "Unsupported platform type" msgstr "不支持的平臺類型" #: apps/xpack/serializers/channel/chat_manage.py:100 msgid "Think: " msgstr "思考内容: " #: apps/xpack/serializers/channel/chat_manage.py:103 #: apps/xpack/serializers/channel/chat_manage.py:105 msgid "AI reply: " msgstr "AI 回復: " #: apps/xpack/serializers/channel/chat_manage.py:318 msgid "Thinking, please wait a moment!" msgstr "思考中,請稍等!" #: apps/xpack/serializers/channel/ding_talk.py:19 #: apps/xpack/serializers/channel/wechat.py:91 #: apps/xpack/serializers/channel/wechat.py:132 #: apps/xpack/serializers/channel/wecom.py:78 #: apps/xpack/serializers/channel/wecom.py:259 msgid "The corresponding platform configuration was not found" msgstr "未找到對應的平臺配置" #: apps/xpack/serializers/channel/ding_talk.py:27 #: apps/xpack/serializers/channel/lark.py:117 msgid "Currently only text messages are supported" msgstr "目前僅支持文本消息" #: apps/xpack/serializers/channel/ding_talk.py:91 #: apps/xpack/serializers/channel/wechat.py:163 #: apps/xpack/serializers/channel/wecom.py:189 msgid "Image download failed, check network" msgstr "圖片下載失敗,請檢查網絡" #: apps/xpack/serializers/channel/ding_talk.py:92 #: apps/xpack/serializers/channel/wechat.py:161 #: apps/xpack/serializers/channel/wecom.py:185 msgid "Please analyze the content of the image." msgstr "請分析圖片內容。" #: apps/xpack/serializers/channel/ding_talk.py:95 msgid "DingTalk application: {user}" msgstr "釘釘智能體: {user}" #: apps/xpack/serializers/channel/ding_talk.py:106 #: apps/xpack/serializers/channel/ding_talk.py:151 msgid "Content generated by AI" msgstr "AI 生成的內容" #: apps/xpack/serializers/channel/lark.py:92 msgid "Lark application: " msgstr "飛書智能體: " #: apps/xpack/serializers/channel/slack.py:116 msgid "The corresponding platform configuration for Slack was not found" msgstr "Slack 的對應平臺配置未找到" #: apps/xpack/serializers/channel/slack.py:206 msgid "Thinking..." msgstr "思考中..." #: apps/xpack/serializers/channel/slack.py:333 msgid "Invalid json format." msgstr "json 格式無效。" #: apps/xpack/serializers/channel/slack.py:339 msgid "Invalid Slack request" msgstr "Slack 請求無效" #: apps/xpack/serializers/channel/slack.py:347 msgid "Slack application: {user}" msgstr "Slack 智能體: {user}" #: apps/xpack/serializers/channel/slack.py:480 msgid "Stop" msgstr "停止" #: apps/xpack/serializers/channel/tools.py:58 #, python-brace-format msgid "" "Thinking about 【{question}】...If you want me to continue answering, please " "reply {trigger_message}" msgstr "思考【{question}】...如果你想讓我繼續回答,請回復 {trigger_message}" #: apps/xpack/serializers/channel/tools.py:158 msgid "" "\n" " ------------\n" "[To be continued, reply \"Continue to answer the question]" msgstr "" "\n" "------------\n" "[待續,回復 \"繼續回答問題]" #: apps/xpack/serializers/channel/tools.py:238 #, python-brace-format msgid "" "To be continued, reply \"{trigger_message}\" to continue answering the " "question" msgstr "待續,回復 \"{trigger_message}\" 繼續回答問題" #: apps/xpack/serializers/channel/wechat.py:143 #, python-brace-format msgid "WeChat Official Account: {account}" msgstr "微信公眾帳號: {account}" #: apps/xpack/serializers/channel/wechat.py:150 #: apps/xpack/serializers/channel/wecom.py:171 #: apps/xpack/serializers/channel/wecom.py:175 msgid "" "The app does not enable the speech-to-text function or the speech-to-text " "function fails." msgstr "智能體未開啟語音轉文字功能或語音轉文字功能失敗。" #: apps/xpack/serializers/channel/wechat.py:189 msgid "Message types not supported yet" msgstr "消息類型暫不支持" #: apps/xpack/serializers/channel/wechat.py:196 msgid "Welcome to subscribe" msgstr "歡迎訂閱" #: apps/xpack/serializers/channel/wecom.py:86 msgid "Enterprise WeChat user: " msgstr "企業微信用戶: " #: apps/xpack/serializers/channel/wecom.py:97 msgid "Enterprise WeChat customer service: " msgstr "企業微信客服: " #: apps/xpack/serializers/channel/wecom.py:134 #: apps/xpack/serializers/channel/wecom.py:150 msgid "This type of message is not supported yet" msgstr "此類型消息暫不支持" #: apps/xpack/serializers/channel/wecom.py:254 msgid "Signature missing" msgstr "籤名缺失" #: apps/xpack/serializers/channel/wecom.py:266 #: apps/xpack/serializers/channel/wecom.py:273 #, python-brace-format msgid "An error occurred while processing the GET request {e}" msgstr "get 請求處理時發生錯誤 {e}" #: apps/xpack/serializers/chat_auth.py:51 msgid "The password is incorrect" msgstr "密碼不正確" #: apps/xpack/serializers/chat_user.py:42 msgid "Some user groups do not exist" msgstr "某些用戶組不存在" #: apps/xpack/serializers/chat_user.py:181 msgid "Is Append" msgstr "是否追加" #: apps/xpack/serializers/chat_user.py:194 msgid "User Group IDs cannot be empty" msgstr "用戶組 IDs 不能為空" #: apps/xpack/serializers/chat_user.py:198 msgid "Some users do not exist" msgstr "某些用戶不存在" #: apps/xpack/serializers/chat_user.py:361 msgid "Sync Type: LOCAL or LDAP" msgstr "同步類型: LOCAL 或 LDAP" #: apps/xpack/serializers/chat_user.py:403 msgid "Unsupported sync type" msgstr "不支持的同步類型" #: apps/xpack/serializers/chat_user.py:412 #: apps/xpack/serializers/chat_user.py:444 #: apps/xpack/serializers/chat_user.py:483 #: apps/xpack/serializers/chat_user.py:510 #: apps/xpack/serializers/chat_user.py:548 #: apps/xpack/serializers/chat_user.py:570 msgid "User group does not exist" msgstr "用戶組不存在" #: apps/xpack/serializers/chat_user.py:451 msgid "User group name already exists" msgstr "用戶組名稱已存在" #: apps/xpack/serializers/chat_user.py:485 msgid "Default user group cannot be deleted" msgstr "默認用戶組不能被刪除" #: apps/xpack/serializers/chat_user.py:550 msgid "User group relation IDs cannot be empty" msgstr "用戶組關係 IDs 不能為空" #: apps/xpack/serializers/chat_user_serializer.py:75 msgid "Invalid access token" msgstr "無效的訪問令牌" #: apps/xpack/serializers/chat_user_serializer.py:102 msgid "The user does not have permission to access the application" msgstr "用戶沒有訪問智能體的權限" #: apps/xpack/serializers/dataset_lark_serializer.py:56 #: apps/xpack/serializers/dataset_lark_serializer.py:299 msgid "app id" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:57 #: apps/xpack/serializers/dataset_lark_serializer.py:300 msgid "app secret" msgstr "" #: apps/xpack/serializers/dataset_lark_serializer.py:58 #: apps/xpack/serializers/dataset_lark_serializer.py:105 #: apps/xpack/serializers/dataset_lark_serializer.py:301 msgid "folder token" msgstr "文件夾令牌" #: apps/xpack/serializers/dataset_lark_serializer.py:60 msgid "embedding model" msgstr "向量模型" #: apps/xpack/serializers/dataset_lark_serializer.py:71 #: apps/xpack/serializers/dataset_lark_serializer.py:311 msgid "Network error or folder token error!" msgstr "網絡錯誤或文件夾令牌錯誤!" #: apps/xpack/serializers/dataset_lark_serializer.py:113 #: apps/xpack/serializers/dataset_lark_serializer.py:155 #: apps/xpack/task/sync.py:308 msgid "Knowledge base not found!" msgstr "知識庫未找到!" #: apps/xpack/serializers/dataset_lark_serializer.py:125 #: apps/xpack/task/sync.py:240 msgid "Failed to get lark document list!" msgstr "獲取飛書文檔列表失敗!" #: apps/xpack/serializers/dataset_lark_serializer.py:147 msgid "Knowledge id" msgstr "知識庫 ID" #: apps/xpack/serializers/dataset_lark_serializer.py:169 msgid "Synchronization is only supported for lark documents" msgstr "僅支持飛書文檔的同步" #: apps/xpack/serializers/license/license_serializers.py:102 #: apps/xpack/serializers/license/license_serializers.py:123 #: apps/xpack/serializers/license/license_tools.py:111 msgid "The license is invalid" msgstr "許可證無效" #: apps/xpack/serializers/license/license_tools.py:136 msgid "License usage limit exceeded." msgstr "License 使用限制已超出。" #: apps/xpack/serializers/license/license_tools.py:160 msgid "The network is busy, try again later." msgstr "網絡繁忙,請稍後再試。" #: apps/xpack/serializers/operate_log_serializer.py:59 msgid "user" msgstr "用戶" #: apps/xpack/serializers/operate_log_serializer.py:61 msgid "ip_address" msgstr "IP 地址" #: apps/xpack/serializers/operate_log_serializer.py:62 msgid "workspace_id" msgstr "工作空間ID" #: apps/xpack/serializers/operate_log_serializer.py:134 msgid "Fail" msgstr "失敗" #: apps/xpack/serializers/operate_log_serializer.py:171 msgid "Menu" msgstr "菜單" #: apps/xpack/serializers/operate_log_serializer.py:172 msgid "Operate" msgstr "操作" #: apps/xpack/serializers/operate_log_serializer.py:173 msgid "Operate user" msgstr "操作用戶" #: apps/xpack/serializers/operate_log_serializer.py:175 msgid "Ip Address" msgstr "IP位址" #: apps/xpack/serializers/operate_log_serializer.py:176 msgid "API Details" msgstr "API詳情" #: apps/xpack/serializers/operate_log_serializer.py:177 msgid "Operate Time" msgstr "操作時間" #: apps/xpack/serializers/platform_serializer.py:12 msgid "app_id is required" msgstr "app_id 是必填項" #: apps/xpack/serializers/platform_serializer.py:13 msgid "app_secret is required" msgstr "app_secret 是必填項" #: apps/xpack/serializers/platform_serializer.py:14 msgid "token is required" msgstr "token 是必填項" #: apps/xpack/serializers/platform_serializer.py:15 msgid "callback_url is required" msgstr "callback_url 是必填項" #: apps/xpack/serializers/platform_serializer.py:21 #: apps/xpack/serializers/platform_serializer.py:30 msgid "App ID is required" msgstr "App ID 是必填項" #: apps/xpack/serializers/platform_serializer.py:23 msgid "Secret is required" msgstr "Secret 是必填項" #: apps/xpack/serializers/platform_serializer.py:24 msgid "Token is required" msgstr "Token 是必填項" #: apps/xpack/serializers/platform_serializer.py:33 msgid "Verification Token is required" msgstr "驗證令牌是必填項" #: apps/xpack/serializers/platform_serializer.py:38 msgid "Client ID is required" msgstr "Client ID 是必填項" #: apps/xpack/serializers/platform_serializer.py:39 msgid "Client Secret is required" msgstr "客戶端密鑰是必填項" #: apps/xpack/serializers/platform_serializer.py:44 msgid "Signing Secret is required" msgstr "籤名密鑰是必填項" #: apps/xpack/serializers/platform_serializer.py:45 msgid "Bot User Token is required" msgstr "機器人用戶令牌是必填項" #: apps/xpack/serializers/platform_serializer.py:66 msgid "Check if the fields are correct" msgstr "檢查欄位是否正確" #: apps/xpack/serializers/platform_serializer.py:155 #, python-brace-format msgid "The platform configuration corresponding to {type} was not found" msgstr "未找到對應 {type} 的平臺配置" #: apps/xpack/serializers/resource_chat_user.py:35 #: apps/xpack/serializers/resource_chat_user.py:111 #: apps/xpack/serializers/resource_chat_user_group.py:18 #: apps/xpack/serializers/resource_chat_user_group.py:86 msgid "Resource id" msgstr "資源ID" #: apps/xpack/serializers/resource_chat_user.py:38 #: apps/xpack/serializers/resource_chat_user.py:112 msgid "User group id" msgstr "用戶組ID" #: apps/xpack/serializers/resource_chat_user.py:94 msgid "Is auth" msgstr "是否授權" #: apps/xpack/serializers/resource_chat_user_group.py:20 msgid "User group name" msgstr "用戶名" #: apps/xpack/serializers/resource_chat_user_group.py:68 msgid "user_group_id" msgstr "用戶組ID" #: apps/xpack/serializers/sso_auth/cas.py:32 msgid "HttpClient query failed: " msgstr "HttpClient 查詢失敗: " #: apps/xpack/serializers/sso_auth/cas.py:58 msgid "CAS authentication failed" msgstr "CAS 認證失敗" #: apps/xpack/serializers/sso_auth/oauth2.py:165 #: apps/xpack/serializers/sso_auth/oauth2.py:184 #: apps/xpack/serializers/sso_auth/oauth2.py:187 msgid "Failed to obtain user information" msgstr "獲取用戶信息失敗" #: apps/xpack/serializers/system_api_key.py:12 msgid "Allow cross domain" msgstr "允許跨域" #: apps/xpack/serializers/system_api_key.py:13 msgid "Cross domain list" msgstr "跨域列表" #: apps/xpack/serializers/system_api_key.py:44 msgid "system API key id" msgstr "系統 API 密鑰 ID" #: apps/xpack/serializers/system_params.py:20 msgid "theme" msgstr "主題" #: apps/xpack/serializers/system_params.py:22 msgid "login logo" msgstr "登錄 logo" #: apps/xpack/serializers/system_params.py:23 msgid "login image" msgstr "登陸圖片" #: apps/xpack/serializers/system_params.py:24 msgid "title" msgstr "標題" #: apps/xpack/serializers/system_params.py:25 msgid "slogan" msgstr "標語" #: apps/xpack/serializers/system_params.py:26 #: apps/xpack/serializers/system_params.py:27 msgid "show user manual" msgstr "顯示用戶手冊" #: apps/xpack/serializers/system_params.py:28 msgid "user manual url" msgstr "用戶手冊網址" #: apps/xpack/serializers/system_params.py:29 msgid "show forum" msgstr "顯示論壇" #: apps/xpack/serializers/system_params.py:30 msgid "forum url" msgstr "論壇網址" #: apps/xpack/serializers/system_params.py:31 msgid "show project" msgstr "顯示項目" #: apps/xpack/serializers/system_params.py:32 msgid "project url" msgstr "項目網址" #: apps/xpack/views/application_setting.py:24 #: apps/xpack/views/application_setting.py:25 #: apps/xpack/views/application_setting.py:26 msgid "Modify Application Settings" msgstr "修改智能體設置" #: apps/xpack/views/application_setting.py:42 #: apps/xpack/views/application_setting.py:43 #: apps/xpack/views/application_setting.py:44 msgid "Get Application Settings" msgstr "獲取智能體設置" #: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53 #: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46 #: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48 msgid "Get authentication types" msgstr "獲取認證類型" #: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70 #: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108 #: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235 #: apps/xpack/views/auth.py:249 msgid "Authentication Configuration" msgstr "認證配置" #: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68 #: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62 #: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64 msgid "Test LDAP connection" msgstr "測試 LDAP 連接" #: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88 #: apps/xpack/views/auth.py:89 msgid "Add or modify authentication configuration" msgstr "添加或修改認證配置" #: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106 #: apps/xpack/views/auth.py:107 msgid "Get authentication configuration" msgstr "獲取認證配置" #: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119 #: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112 #: apps/xpack/views/chat_user_auth.py:113 #: apps/xpack/views/chat_user_auth.py:114 msgid "Ldap Log in" msgstr "LDAP 登錄" #: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138 #: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176 #: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208 #: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286 #: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327 #: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368 msgid "Three-party login" msgstr "三方登錄" #: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136 #: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129 #: apps/xpack/views/chat_user_auth.py:130 #: apps/xpack/views/chat_user_auth.py:131 msgid "CAS Log in" msgstr "CAS 登錄" #: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155 #: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148 #: apps/xpack/views/chat_user_auth.py:149 #: apps/xpack/views/chat_user_auth.py:150 msgid "OIDC Log in" msgstr "OIDC 登錄" #: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174 #: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167 #: apps/xpack/views/chat_user_auth.py:168 #: apps/xpack/views/chat_user_auth.py:169 msgid "OAuth2 Log in" msgstr "OAuth2 登錄" #: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192 #: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220 #: apps/xpack/views/chat_user_auth.py:221 #: apps/xpack/views/chat_user_auth.py:222 msgid "Scan code login type" msgstr "掃碼登錄類型" #: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206 #: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220 #: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222 #: apps/xpack/views/chat_user_auth.py:234 #: apps/xpack/views/chat_user_auth.py:235 #: apps/xpack/views/chat_user_auth.py:236 #: apps/xpack/views/chat_user_auth.py:249 #: apps/xpack/views/chat_user_auth.py:250 #: apps/xpack/views/chat_user_auth.py:251 msgid "Get platform information" msgstr "獲取平臺信息" #: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233 #: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261 #: apps/xpack/views/chat_user_auth.py:262 #: apps/xpack/views/chat_user_auth.py:263 msgid "Modify platform information" msgstr "修改平臺信息" #: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247 #: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275 #: apps/xpack/views/chat_user_auth.py:276 #: apps/xpack/views/chat_user_auth.py:277 msgid "Test platform connection" msgstr "測試平臺連接" #: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264 #: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292 #: apps/xpack/views/chat_user_auth.py:293 #: apps/xpack/views/chat_user_auth.py:294 msgid "DingTalk callback" msgstr "釘釘回調" #: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284 #: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312 #: apps/xpack/views/chat_user_auth.py:313 #: apps/xpack/views/chat_user_auth.py:314 msgid "DingTalk OAuth2 callback" msgstr "釘釘 OAuth2 回調" #: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305 #: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333 #: apps/xpack/views/chat_user_auth.py:334 #: apps/xpack/views/chat_user_auth.py:335 msgid "WeCom callback" msgstr "企業微信回調" #: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325 #: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353 #: apps/xpack/views/chat_user_auth.py:354 #: apps/xpack/views/chat_user_auth.py:355 msgid "WeCom OAuth2 callback" msgstr "企業微信 OAuth2 回調" #: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346 #: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374 #: apps/xpack/views/chat_user_auth.py:375 #: apps/xpack/views/chat_user_auth.py:376 msgid "Lark callback" msgstr "飛書回調" #: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366 #: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394 #: apps/xpack/views/chat_user_auth.py:395 #: apps/xpack/views/chat_user_auth.py:396 msgid "Lark OAuth2 callback" msgstr "飛書 OAuth2 回調" #: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65 #: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252 #: apps/xpack/views/chat_user_auth.py:264 #: apps/xpack/views/chat_user_auth.py:278 msgid "Chat User/Authentication Configuration" msgstr "對話用戶/認證配置" #: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83 #: apps/xpack/views/chat_user_auth.py:84 msgid "Add or modify Chat/Authentication Configuration" msgstr "添加或修改對話/認證配置" #: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100 #: apps/xpack/views/chat_user_auth.py:101 msgid "Get Authentication Configuration" msgstr "獲取認證配置" #: apps/xpack/views/chat_user_auth.py:102 msgid "Chat User/login authentication" msgstr "對話用戶/登錄認證" #: apps/xpack/views/chat_user_auth.py:115 #: apps/xpack/views/chat_user_auth.py:132 #: apps/xpack/views/chat_user_auth.py:151 #: apps/xpack/views/chat_user_auth.py:170 #: apps/xpack/views/chat_user_auth.py:223 #: apps/xpack/views/chat_user_auth.py:237 #: apps/xpack/views/chat_user_auth.py:295 #: apps/xpack/views/chat_user_auth.py:315 #: apps/xpack/views/chat_user_auth.py:336 #: apps/xpack/views/chat_user_auth.py:356 #: apps/xpack/views/chat_user_auth.py:377 #: apps/xpack/views/chat_user_auth.py:397 msgid "Chat User/Three-party login" msgstr "對話用戶/三方登錄" #: apps/xpack/views/chat_user_auth.py:187 msgid "Chat User/login" msgstr "對話用戶/登錄" #: apps/xpack/views/chat_user_auth.py:414 #: apps/xpack/views/chat_user_auth.py:415 #: apps/xpack/views/chat_user_auth.py:416 msgid "Application Password Certification" msgstr "智能體密碼認證" #: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32 #: apps/xpack/views/license.py:33 msgid "Get license information" msgstr "獲取許可證信息" #: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44 msgid "Update license information" msgstr "更新許可證信息" #: apps/xpack/views/license.py:43 msgid "Update license information by uploading a new license file" msgstr "通過上傳新許可證文件更新許可證信息" #: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22 #: apps/xpack/views/operate_log.py:23 msgid "Get menu operate log" msgstr "獲取菜單操作日誌" #: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41 #: apps/xpack/views/operate_log.py:57 msgid "System operate log" msgstr "系統操作日誌" #: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37 #: apps/xpack/views/operate_log.py:38 msgid "Get paginated operate log" msgstr "獲取分頁操作日誌" #: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55 #: apps/xpack/views/operate_log.py:56 msgid "Export operate log" msgstr "導出操作日誌" #: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61 #: apps/xpack/views/platform.py:62 msgid "Get platform configuration" msgstr "獲取平臺配置" #: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79 msgid "Application/application access" msgstr "智能體/智能體訪問" #: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74 #: apps/xpack/views/platform.py:75 msgid "Update platform configuration" msgstr "更新平臺配置" #: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95 #: apps/xpack/views/platform.py:96 msgid "Get platform status" msgstr "獲取平臺狀態" #: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118 msgid "Application/Get platform status" msgstr "智能體/獲取平臺狀態" #: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114 #: apps/xpack/views/platform.py:115 msgid "Update platform status" msgstr "更新平臺狀態" #: apps/xpack/views/resource_chat_user.py:27 #: apps/xpack/views/resource_chat_user.py:28 #: apps/xpack/views/resource_chat_user.py:29 msgid "Get Resource chat user List" msgstr "獲取資源聊天用戶列表" #: apps/xpack/views/resource_chat_user.py:32 #: apps/xpack/views/resource_chat_user.py:54 #: apps/xpack/views/resource_chat_user.py:77 #: apps/xpack/views/system_chat_user_group.py:24 #: apps/xpack/views/system_chat_user_group.py:45 #: apps/xpack/views/system_chat_user_group.py:67 msgid "Chat user" msgstr "聊天用戶" #: apps/xpack/views/resource_chat_user.py:48 #: apps/xpack/views/resource_chat_user.py:49 #: apps/xpack/views/resource_chat_user.py:50 msgid "Edit Resource chat user List" msgstr "編輯資源聊天用戶列表" #: apps/xpack/views/resource_chat_user.py:72 #: apps/xpack/views/resource_chat_user.py:73 #: apps/xpack/views/resource_chat_user.py:74 msgid "Get Resource chat user page List" msgstr "獲取資源聊天用戶分頁列表" #: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20 #: apps/xpack/views/system_api_key.py:21 msgid "Create SystemAPIKey" msgstr "創建系統 API 密鑰" #: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35 #: apps/xpack/views/system_api_key.py:36 msgid "Get SystemAPIKey List" msgstr "獲取系統 API 密鑰列表" #: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51 #: apps/xpack/views/system_api_key.py:52 msgid "Update SystemAPIKey" msgstr "更新系統 API 密鑰" #: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67 #: apps/xpack/views/system_api_key.py:68 msgid "Delete SystemAPIKey" msgstr "刪除系統 API 密鑰" #: apps/xpack/views/system_chat_user.py:60 #: apps/xpack/views/system_chat_user.py:76 #: apps/xpack/views/system_chat_user.py:89 #: apps/xpack/views/system_chat_user.py:102 #: apps/xpack/views/system_chat_user.py:113 #: apps/xpack/views/system_chat_user.py:131 #: apps/xpack/views/system_chat_user.py:146 #: apps/xpack/views/system_chat_user.py:163 #: apps/xpack/views/system_chat_user.py:180 #: apps/xpack/views/system_chat_user.py:200 #: apps/xpack/views/system_chat_user.py:217 msgid "System/Chat user" msgstr "系統/對話用戶" #: apps/xpack/views/system_chat_user.py:73 #: apps/xpack/views/system_chat_user.py:74 #: apps/xpack/views/system_chat_user.py:75 msgid "Get chat user list" msgstr "獲取對話用戶列表" #: apps/xpack/views/system_chat_user.py:214 #: apps/xpack/views/system_chat_user.py:216 msgid "Sync chat users" msgstr "同步對話用戶" #: apps/xpack/views/system_chat_user.py:215 msgid "Sync chat users from external source" msgstr "從外部源同步對話用戶" #: apps/xpack/views/system_chat_user.py:235 #: apps/xpack/views/system_chat_user.py:247 #: apps/xpack/views/system_chat_user.py:261 #: apps/xpack/views/system_chat_user.py:279 #: apps/xpack/views/system_chat_user.py:301 #: apps/xpack/views/system_chat_user.py:321 msgid "System/User Group" msgstr "系統/用戶組" #: apps/xpack/views/system_chat_user_group.py:19 #: apps/xpack/views/system_chat_user_group.py:20 #: apps/xpack/views/system_chat_user_group.py:21 msgid "Get Resource chat user group List" msgstr "獲取資源聊天用戶組列表" #: apps/xpack/views/system_chat_user_group.py:39 #: apps/xpack/views/system_chat_user_group.py:40 #: apps/xpack/views/system_chat_user_group.py:41 msgid "Edit Resource chat user group List" msgstr "編輯資源聊天用戶組列表" #: apps/xpack/views/system_chat_user_group.py:62 #: apps/xpack/views/system_chat_user_group.py:64 msgid "Get Resource chat user group page List" msgstr "獲取資源聊天用戶組分頁列表" #: apps/xpack/views/system_chat_user_group.py:63 msgid "Get Resource chat user page group List" msgstr "獲取資源聊天用戶分頁組列表" #: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23 #: apps/xpack/views/system_params.py:24 msgid "View appearance settings" msgstr "查看外觀設置" #: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40 #: apps/xpack/views/system_params.py:41 msgid "Update appearance settings" msgstr "更新外觀設置" msgid "Application Access" msgstr "智能體介入" msgid "Display execution details" msgstr "是否顯示執行詳情" msgid "LOCAL" msgstr "帳號登入" msgid "CAS" msgstr "CAS" msgid "LDAP" msgstr "LDAP" msgid "OIDC" msgstr "OIDC" msgid "OAuth2" msgstr "OAuth2" msgid "dingtalk" msgstr "钉钉" msgid "wecom" msgstr "企业微信" msgid "lark" msgstr "飞书" msgid "Get tool list" msgstr "獲取工具列表" msgid "Setting" msgstr "設置" msgid "Get verification results" msgstr "獲取驗證結果" msgid "Validation" msgstr "驗證" msgid "Models Resource" msgstr "模型資源" msgid "Tools Resource" msgstr "工具資源" msgid "Get resource model list" msgstr "獲取資源模型列表" msgid "System Model" msgstr "系統模型" msgid "Dialogue users" msgstr "對話用戶" msgid "Conversation log" msgstr "對話日誌" msgid "Public access link" msgstr "公共訪問鏈接" msgid "User management" msgstr "用戶管理" msgid "Chat User/logout" msgstr "對話用戶/登出" msgid "Paragraph" msgstr "段落" msgid "User group" msgstr "用戶組" msgid "Remove member from user group" msgstr "從用戶組中移除成員" msgid "Create a web site knowledge base" msgstr "創建web知識庫" msgid "Modify knowledge base information" msgstr "修改知識庫信息" msgid "Delete knowledge base" msgstr "刪除知識庫" msgid "model" msgstr "模型" msgid "Batch add user to group" msgstr "批量添加用戶到組" msgid "Add internal tool" msgstr "添加内置工具" msgid "Batch generate related" msgstr "批量生成相關" msgid "Update personal system API_KEY" msgstr "更新個人系統 API KEY" msgid "Delete user group" msgstr "刪除用戶組" msgid "Add user" msgstr "添加用戶" msgid "folder" msgstr "文件夾" msgid "Create or update user group" msgstr "創建或更新用戶組" msgid "Edit folder" msgstr "編輯文件夾" msgid "Email settings" msgstr "郵件設置" msgid "trial listening" msgstr "試聽" msgid "Add member to user group" msgstr "添加成員到用戶組" msgid "System" msgstr "系統" msgid "Shared Knowledge/Document" msgstr "共享知識/文件" msgid "System Application" msgstr "系統智能體" msgid "Hit-Test" msgstr "命中測試" msgid "Export Application" msgstr "導出智能體" msgid "Add ApiKey" msgstr "添加 API KEY" msgid "Delete application API_KEY" msgstr "删除智能體 API KEY" msgid "knowledge Base" msgstr "知識庫" msgid "API KEY" msgstr "API KEY" msgid "Download" msgstr "下載" msgid "Delete personal system API_KEY" msgstr "删除個人系統API KEY" msgid "Add personal system API_KEY" msgstr "添加個人系統API KEY" msgid "Generate related documents" msgstr "生成相關文檔" msgid "Modify application access token" msgstr "修改智能體程序訪問權杖" msgid "File not exist. Only manually uploaded documents are supported" msgstr "文件不存在, 僅支持手動上傳的文檔" msgid "Resource" msgstr "資源管理" msgid "LDAP configuration not found or not active" msgstr "LDAP 配置未找到或未激活" msgid "Lark configuration not found or not active" msgstr "Lark 配置未找到或未激活" msgid "Failed to get Lark collaborators" msgstr "獲取 Lark 協作者失敗" msgid "Failed to get Lark user details" msgstr "獲取 Lark 用戶詳情失敗" msgid "WeCom configuration not found or not active" msgstr "WeCom 配置未找到或未激活" msgid "Failed to get WeCom access token" msgstr "獲取 WeCom 訪問權杖失敗" msgid "Failed to get WeCom agent info" msgstr "獲取 WeCom 智能助手信息失敗" msgid "Failed to get WeCom department users" msgstr "獲取 WeCom 部門用戶失敗" msgid "Failed to get WeCom user info" msgstr "獲取 WeCom 用戶詳情失敗" msgid "Publish status" msgstr "發佈狀態" msgid "Unpublished" msgstr "未發佈" msgid "Published" msgstr "已發佈" msgid "users_permission" msgstr "用戶許可權" msgid "Get user authorization status of resource" msgstr "獲取資源對用戶的授權狀態" msgid "Edit user authorization status of resource" msgstr "修改資源對用戶的授權狀態" msgid "Get user authorization status of resource by page" msgstr "分頁獲取資源對用戶的授權狀態" msgid "Obtain resource authorization list by page" msgstr "分頁獲取資源授權清單" msgid "Engine model type" msgstr "引擎模型類型" msgid "If not passed, the default value is 16k_zh (Chinese universal)" msgstr "如果未傳遞,默認值為 16k_zh(中文通用)" msgid "Chinese telephone universal" msgstr "中文電話通用" msgid "English telephone universal" msgstr "英文電話通用" msgid "Commonly used in Chinese" msgstr "中文常用" msgid "Chinese, English, and Guangdong" msgstr "中文、英文和廣東話" msgid "Chinese medical" msgstr "中文醫療" msgid "English" msgstr "英文" msgid "Cantonese" msgstr "粵語" msgid "Japanese" msgstr "日語" msgid "Korean" msgstr "韓語" msgid "Vietnamese" msgstr "越南語" msgid "Malay language" msgstr "馬來語" msgid "Indonesian language" msgstr "印尼語" msgid "Filipino language" msgstr "菲律賓語" msgid "Thai" msgstr "泰語" msgid "Portuguese" msgstr "葡萄牙語" msgid "Turkish" msgstr "土耳其語" msgid "Arabic" msgstr "阿拉伯語" msgid "Spanish" msgstr "西班牙語" msgid "Hindi" msgstr "印地語" msgid "French" msgstr "法語" msgid "German" msgstr "德語" msgid "Multiple dialects, supporting 23 dialects" msgstr "多種方言,支持 23 種方言" msgid "This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects." msgstr "本介面用於識別 60 秒之內的短音頻文件。支援中文普通話、英語、粵語、日語、越南語、馬來語、印度尼西亞語、菲律賓語、泰語、葡萄牙語、土耳其語、阿拉伯語、印地語、法語、德語及 23 種漢語方言。" msgid "CueWord" msgstr "提示詞" msgid "If not passed, the default value is What is this audio saying? Only answer the audio content" msgstr "如果未傳遞,預設值為這段音訊在說什麼,只回答音訊的內容" msgid "The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text." msgstr "Qwen-Omni系列模型支持輸入多種模態的數據,包括視頻、音訊、圖片、文字,並輸出音訊與文字" msgid "resource authorization" msgstr "資源授權" msgid "The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition." msgstr "基於Qwen-Audio的端到端語音辨識大模型,支持3分鐘以內的音訊識別,現時主要支持中英文識別。" msgid "If not passed, the default value is 'zh'" msgstr "如果未傳遞,則預設值為'zh'" msgid "System resources authorization" msgstr "系統資源授權" msgid "This folder contains resources that you dont have permission" msgstr "此資料夾包含您沒有許可權的資源" msgid "Text to Video" msgstr "文生視頻" msgid "Image to Video" msgstr "圖生視頻" msgid "Authentication failed. Please verify that the parameters are correct" msgstr "認證失敗,請檢查參數是否正確" msgid "Chat context" msgstr "聊天上下文" msgid "Prompt template" msgstr "提示詞範本" msgid "generate prompt" msgstr "生成提示詞" msgid "Watermark" msgstr "水印" msgid "Whether to add watermark" msgstr "是否添加水印" msgid "Resolution" msgstr "分辨率" msgid "Ratio" msgstr "比例" msgid "Duration" msgstr "時長" msgid "Failed to generate video" msgstr "生成視頻失敗" msgid "password" msgstr "密码登录" msgid "Failed to obtain the image" msgstr "獲取圖片失敗" msgid "Update auth setting" msgstr "更新認證設置" msgid "If not passed, the default value is streaming_asr_demo" msgstr "如果未傳入,則預設值為 streaming_asr_demo" msgid "If not passed, the default value is 16000" msgstr "如果未傳入,則預設值為 16000" msgid "Sample Rate" msgstr "採樣率" msgid "Captcha is required" msgstr "驗證碼是必填項" msgid "Tag" msgstr "標籤管理" msgid "Tag Setting" msgstr "標籤設定" msgid "Download Original Document" msgstr "下載原文件" msgid "Replace Original Document" msgstr "替換原文件" msgid "Update License" msgstr "更新許可證" msgid "Tag Key" msgstr "標籤" msgid "Tag Value" msgstr "標籤值" msgid "Tag id does not exist" msgstr "標籤ID不存在" msgid "Tag key already exists" msgstr "標籤已存在" msgid "Tag value already exists" msgstr "標籤值已存在" msgid "Non-existent id" msgstr "不存在的ID" msgid "No permission for the target folder" msgstr "沒有目標資料夾的權限" msgid "Application token usage statistics" msgstr "智能體令牌使用統計" msgid "Application top question statistics" msgstr "智能體提問次數統計" msgid "SAML2 Metadata" msgstr "SAML2 元數據" msgid "SAML2 Log in" msgstr "SAML2 登入" msgid "SAML2 SSO" msgstr "SAML2 單點登入" msgid "Workflow" msgstr "工作流" msgid "Web source url" msgstr "Web 根地址" msgid "Web knowledge selector" msgstr "選擇器" msgid "The default is body, you can enter .classname/#idname/tagname" msgstr "默認為 body,可輸入 .classname/#idname/tagname" msgid "Please enter the Web root address" msgstr "請輸入 Web 根地址" msgid "File size exceeds limit" msgstr "文件大小超出限制" msgid "File upload is not enabled" msgstr "文件上傳未啟用" msgid "Blocked unsafe redirect to internal host" msgstr "阻止不安全的重定向到內部主機" msgid "Audio file recognition - Tongyi Qwen" msgstr "錄音文件識別-通義千問" msgid "Real-time speech recognition - Fun-ASR/Paraformer" msgstr "實時語音識別-Fun-ASR/Paraformer" msgid "Qwen-Omni" msgstr "多模態" msgid "Super-humanoid: Lingxiaoxuan Flow" msgstr "聆小璇" msgid "Super-humanoid: Lingyuyan Flow" msgstr "聆玉言" msgid "Super-humanoid: Lingfeiyi Flow" msgstr "聆飛逸" msgid "Super-humanoid: Lingxiaoyue Flow" msgstr "聆小玥" msgid "Super-humanoid: Sun Dasheng Flow" msgstr "孫大聖" msgid "Super-humanoid: Lingyuzhao Flow" msgstr "聆玉昭" msgid "Super-humanoid: Lingxiaotang Flow" msgstr "聆小糖" msgid "Super-humanoid: Lingxiaorong Flow" msgstr "聆小蓉" msgid "Super-humanoid: Xinyun Flow" msgstr "心雲" msgid "Super-humanoid: Grant (EN)" msgstr "Grant" msgid "Super-humanoid: Lila (EN)" msgstr "Lila" msgid "Super-humanoid: Lingwanwan Pro" msgstr "聆萬萬" msgid "Super-humanoid: Yiyi Pro" msgstr "依依" msgid "Super-humanoid: Huifangnv Pro" msgstr "惠芳女" msgid "Super-humanoid: Lingxiaoying Pro" msgstr "聆小穎" msgid "Super-humanoid: Lingfeibo Pro" msgstr "聆飛博" msgid "Super-humanoid: Lingyuyan Pro" msgstr "聆玉言" msgid "Login failed %s times, account will be locked, you have %s more chances !" msgstr "登录失败 %s 次,账号将被锁定,您还有 %s 次机会!" msgid "This account has been locked for %s minutes, please try again later" msgstr "該帳號已被鎖定 %s 分鐘,請稍後再試" msgid "User does not have permission to use API Key" msgstr "使用者沒有使用 API Key 的權限" msgid "Import knowledge workflow" msgstr "匯入知識工作流" msgid "Export knowledge workflow" msgstr "匯出知識工作流" msgid "Role IDs cannot be empty" msgstr "角色 ID 不能为空" msgid "Some roles do not exist" msgstr "部分角色不存在" msgid "Authorized pagination list for obtaining resources" msgstr "獲取資源的關係分頁清單" msgid "Resources mapping" msgstr "資源映射" msgid "Batch set user roles" msgstr "批量設置用戶角色" msgid "Role Setting cannot be empty" msgstr "角色設置不能為空" msgid "View related resources" msgstr "查看關聯資源" msgid "Feedback reason" msgstr "反饋理由" msgid "Other reason content" msgstr "其他反饋理由內容" msgid "accurate" msgstr "內容準確" msgid "complete" msgstr "內容完善" msgid "inaccurate" msgstr "內容不準確" msgid "incomplete" msgstr "內容不完善" msgid "Secret key is invalid" msgstr "密鑰無效" msgid "Secret key is expired" msgstr "密鑰已過期" msgid "Online Usage" msgstr "線上使用" msgid "API Call" msgstr "API 調用" msgid "Enterprise WeChat" msgstr "企業微信應用" msgid "WeChat Public Account" msgstr "微信公眾號" msgid "Lark" msgstr "飛書應用" msgid "DingTalk" msgstr "釘釘應用" msgid "Enterprise WeChat Robot" msgstr "企業微信機器人" msgid "Trigger" msgstr "觸發器" msgid "Slack" msgstr "Slack 應用" msgid "Root Directory" msgstr "根目錄" msgid "Create trigger" msgstr "建立觸發器" msgid "Get the trigger list" msgstr "取得觸發器清單" msgid "Get trigger details" msgstr "取得觸發器詳情" msgid "Modify the trigger" msgstr "修改觸發器" msgid "Delete the trigger" msgstr "刪除觸發器" msgid "Delete trigger in batches" msgstr "批次刪除觸發器" msgid "Activate trigger in batches" msgstr "批次啟用/停用觸發器" msgid "Get the trigger list by page" msgstr "分頁取得觸發器清單" msgid "Create trigger in source" msgstr "資源端建立觸發器" msgid "Get the trigger list of source" msgstr "取得資源端觸發器清單" msgid "Get Task source trigger details" msgstr "取得資源端觸發器詳情" msgid "Delete the task source trigger" msgstr "刪除資源端觸發器" msgid "Get the task list of triggers" msgstr "取得觸發器任務清單" msgid "Retrieve detailed records of tasks executed by the trigger." msgstr "取得由該觸發器執行的任務詳細記錄。" msgid "Get a paginated list of execution records for trigger tasks." msgstr "取得觸發器任務執行記錄的分頁清單。" msgid "%s must be an array" msgstr "%s 必須是陣列類型" msgid "%s must not be empty" msgstr "%s 不能為空" msgid "%s values must be between %s and %s" msgstr "%s 的值必須在 %s 到 %s 之間" msgid "Invalid time format: %s, must be HH:MM (e.g., 09:00)" msgstr "時間格式無效: %s,必須是 HH:MM 格式 (例如: 09:00)" msgid "schedule_type must be one of %s" msgstr "schedule_type 必須是以下值之一: %s" msgid "interval_value must be an integer greater than or equal to 1" msgstr "interval_value 必須是大於或等於 1 的整數" msgid "interval_unit must be one of %s" msgstr "interval_unit 必須是以下值之一: %s" msgid "body must be an array" msgstr "body 必須是陣列類型" msgid "Error trigger type" msgstr "觸發器類型錯誤" msgid "The following id does not exist: %s" msgstr "以下 id 不存在: %s" msgid "%s must be a dict" msgstr "%s 必須是字典類型" msgid "input_field_list must be a dict" msgstr "input_field_list 必須是字典類型" msgid "%s type requires %s field" msgstr "%s 類型需要 %s 欄位" msgid "trigger name" msgstr "觸發器名稱" msgid "trigger description" msgstr "觸發器描述" msgid "trigger setting" msgstr "觸發器設定" msgid "Trigger ID" msgstr "觸發器ID" msgid "Trigger task can not be empty" msgstr "觸發器任務不能為空" msgid "%s id does not exist" msgstr "%s id 不存在" msgid "Trigger id does not exist" msgstr "觸發器 id 不存在" msgid "Trigger not found" msgstr "未找到觸發器" msgid "Trigger must have at least one task" msgstr "觸發器必須至少有一個任務" msgid "Trigger task number must be one" msgstr "觸發器任務數量必須為一個" msgid "Incorrect trigger task" msgstr "觸發器任務不正確" msgid "Trigger task ID" msgstr "觸發器任務ID" msgid "Trigger task record ID" msgstr "觸發器任務記錄ID" msgid "Trigger task record id does not exist" msgstr "觸發器任務記錄 id 不存在" msgid "Order field" msgstr "排序欄位" msgid "System Trigger" msgstr "系統觸發器" msgid "Get the System trigger list of source" msgstr "取得來源的系統觸發器清單" msgid "Get System Task source trigger details" msgstr "取得系統任務來源觸發器詳情" msgid "Modify the System task source trigger" msgstr "修改系統任務來源觸發器" msgid "Modify the task source trigger" msgstr "修改任務來源觸發器" msgid "Delete the System task source trigger" msgstr "刪除系統任務來源觸發器" msgid "Invalid source type" msgstr "無效的來源類型" msgid "Shared tool is not supported" msgstr "不支援共享工具" msgid "Read Trigger" msgstr "檢視觸發器" msgid "Create Trigger" msgstr "建立觸發器" msgid "Edit Trigger" msgstr "編輯觸發器" msgid "Delete Trigger" msgstr "刪除觸發器" msgid "Read execute record" msgstr "檢視執行記錄" msgid "ADMIN" msgstr "系統管理員" msgid "WORKSPACE_MANAGE" msgstr "空間管理員" msgid "USER" msgstr "普通用戶" msgid "Generate share link" msgstr "產生分享連結" msgid "Chat record link" msgstr "聊天記錄連結" msgid "Get chat record by share link" msgstr "透過分享連結取得聊天記錄" msgid "Invalid chat record ids" msgstr "無效的聊天記錄ID" msgid "Share link does not exist" msgstr "分享連結不存在" msgid "Chat has been deleted" msgstr "聊天記錄已被刪除" msgid "cron type requires cron_expression field" msgstr "cron 類型需要 cron_expression 欄位" msgid "Invalid cron expression: %s" msgstr "Cron 表達式不合法:%s" msgid "Batch Remove Documents from Tag" msgstr "批量刪除標籤下的文件" msgid "Document does not belong to current knowledge" msgstr "文件不屬於當前知識庫" msgid "Move an application" msgstr "移動應用程序" ================================================ FILE: apps/manage.py ================================================ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main() ================================================ FILE: apps/maxkb/__init__.py ================================================ ================================================ FILE: apps/maxkb/asgi.py ================================================ """ ASGI config for maxkb project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ """ import os from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings') application = get_asgi_application() ================================================ FILE: apps/maxkb/conf.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: conf.py @date:2025/4/11 16:58 @desc: """ import errno import logging import os import yaml BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) logger = logging.getLogger('maxkb.conf') class Config(dict): defaults = { # 数据库相关配置 "DB_HOST": "127.0.0.1", "DB_PORT": 5432, "DB_USER": "root", "DB_PASSWORD": "Password123@postgres", "DB_ENGINE": "dj_db_conn_pool.backends.postgresql", "DB_MAX_OVERFLOW": 80, 'LOCAL_MODEL_HOST': '127.0.0.1', 'LOCAL_MODEL_PORT': '11636', 'LOCAL_MODEL_PROTOCOL': "http", 'LOCAL_MODEL_HOST_WORKER': 1, # 语言 'LANGUAGE_CODE': 'zh-CN', "DEBUG": False, # redis host 'REDIS_HOST': '127.0.0.1', # 端口 'REDIS_PORT': 6379, # 密码 'REDIS_PASSWORD': 'Password123@redis', # 库 'REDIS_DB': 0, # 最大连接数 'REDIS_MAX_CONNECTIONS': 100 } def get_debug(self) -> bool: return self.get('DEBUG') if 'DEBUG' in self else True def get_time_zone(self) -> str: return self.get('TIME_ZONE') if 'TIME_ZONE' in self else 'Asia/Shanghai' def get_db_setting(self) -> dict: return { "NAME": self.get('DB_NAME'), "HOST": self.get('DB_HOST'), "PORT": self.get('DB_PORT'), "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": 1800, "PRE_PING": True, "TIMEOUT": 30 } } def get_cache_setting(self): redis_config = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': f'redis://{self.get("REDIS_HOST")}:{self.get("REDIS_PORT")}/{self.get("REDIS_DB")}', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', "PASSWORD": self.get("REDIS_PASSWORD"), "CONNECTION_POOL_KWARGS": {"max_connections": int(self.get("REDIS_MAX_CONNECTIONS"))} }, }, } if self.get('REDIS_SENTINEL_SENTINELS') is not None: sentinels_str = self.get('REDIS_SENTINEL_SENTINELS') sentinels = [ (host.strip(), int(port)) for hostport in sentinels_str.split(',') for host, port in [hostport.strip().split(':')] ] redis_config['default']['LOCATION'] = f'redis://{self.get("REDIS_SENTINEL_MASTER")}/{self.get("REDIS_DB")}' redis_config['default']['OPTIONS'].update({ 'CLIENT_CLASS': 'django_redis.client.SentinelClient', 'SENTINELS': sentinels, 'SENTINEL_MASTER': self.get('REDIS_SENTINEL_MASTER'), 'PASSWORD': self.get("REDIS_PASSWORD"), }) return redis_config def get_language_code(self): return self.get('LANGUAGE_CODE', 'zh-CN') def get_log_level(self): return self.get('LOG_LEVEL', 'DEBUG') def get_sandbox_python_package_paths(self): return self.get('SANDBOX_PYTHON_PACKAGE_PATHS', '/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages') def get_admin_path(self): return self.get('ADMIN_PATH', '/admin') def get_chat_path(self): return self.get('CHAT_PATH', '/chat') def get_session_timeout(self): return int(self.get('SESSION_TIMEOUT', 28800)) def __init__(self, *args): super().__init__(*args) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) def __getitem__(self, item): return self.get(item) def __getattr__(self, item): return self.get(item) class ConfigManager: config_class = Config def __init__(self, root_path=None): self.root_path = root_path self.config = self.config_class() for key in self.config_class.defaults: self.config[key] = self.config_class.defaults[key] def from_mapping(self, *mapping, **kwargs): """Updates the config like :meth:`update` ignoring items with non-upper keys. .. versionadded:: 0.11 """ mappings = [] if len(mapping) == 1: if hasattr(mapping[0], 'items'): mappings.append(mapping[0].items()) else: mappings.append(mapping[0]) elif len(mapping) > 1: raise TypeError( 'expected at most 1 positional argument, got %d' % len(mapping) ) mappings.append(kwargs.items()) for mapping in mappings: for (key, value) in mapping: if key.isupper(): self.config[key] = value return True def from_yaml(self, filename, silent=False): if self.root_path: filename = os.path.join(self.root_path, filename) try: with open(filename, 'rt', encoding='utf8') as f: obj = yaml.safe_load(f) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False e.strerror = 'Unable to load configuration file (%s)' % e.strerror raise if obj: return self.from_mapping(obj) return True def load_from_yml(self): for i in ['config_example.yml', 'config.yaml', 'config.yml']: if not os.path.isfile(os.path.join(self.root_path, i)): continue loaded = self.from_yaml(i) if loaded: return True msg = f""" 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()) <= 0: 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_REDIS_HOST:缓存数据库主机 MAXKB_REDIS_PORT:缓存数据库端口 MAXKB_REDIS_PASSWORD:缓存数据库密码 MAXKB_REDIS_DB:缓存数据库 MAXKB_REDIS_MAX_CONNECTIONS:缓存数据库最大连接数 """ raise ImportError(msg) self.from_mapping(config) return True @classmethod def load_user_config(cls, root_path=None, config_class=None): config_class = config_class or Config cls.config_class = config_class if not root_path: root_path = PROJECT_DIR manager = cls(root_path=root_path) config_type = os.environ.get('MAXKB_CONFIG_TYPE') if config_type is None or config_type != 'ENV': manager.load_from_yml() else: manager.load_from_env() config = manager.config return config ================================================ FILE: apps/maxkb/const.py ================================================ # -*- coding: utf-8 -*- # import os from dotenv import load_dotenv from .conf import ConfigManager __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOG_DIR = os.path.join('/', 'opt', 'maxkb', 'logs') PROJECT_DIR = os.path.dirname(BASE_DIR) VERSION = '2.0.0' # load environment variables from .env file load_dotenv() # print(os.getenv('MAXKB_CONFIG')) if os.getenv('MAXKB_CONFIG') is not None: CONFIG = ConfigManager.load_user_config(root_path=PROJECT_DIR) else: CONFIG = ConfigManager.load_user_config(root_path=os.path.abspath('/opt/maxkb/conf')) ================================================ FILE: apps/maxkb/settings/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/11 16:39 @desc: """ from .base import * from .logging import * from .auth import * from .lib import * from .mem import * ================================================ FILE: apps/maxkb/settings/auth/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/11/5 14:50 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/maxkb/settings/auth/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: auth.py @date:2024/7/9 18:47 @desc: """ AUTH_HANDLES = [ ] CHAT_AUTH_HANDLES = [ ] ================================================ FILE: apps/maxkb/settings/auth/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: auth.py @date:2024/7/9 18:47 @desc: """ USER_TOKEN_AUTH = 'common.auth.handle.impl.user_token.UserToken' CHAT_ANONYMOUS_USER_AURH = 'common.auth.handle.impl.chat_anonymous_user_token.ChatAnonymousUserToken' APPLICATION_KEY_AUTH = 'common.auth.handle.impl.application_key.ApplicationKey' AUTH_HANDLES = [ USER_TOKEN_AUTH ] CHAT_AUTH_HANDLES = [ CHAT_ANONYMOUS_USER_AURH, APPLICATION_KEY_AUTH ] ================================================ FILE: apps/maxkb/settings/base/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/5 14:53 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/maxkb/settings/base/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py @date:2025/11/5 14:53 @desc: """ from pathlib import Path from ...const import CONFIG, PROJECT_DIR import os from django.utils.translation import gettext_lazy as _ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = CONFIG.get("SECRET_KEY") or 'django-insecure-zm^1_^i5)3gp^&0io6zg72&z!a*d=9kf9o2%uft+27l)+t(#3e' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = CONFIG.get_debug() ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'local_model', ] MIDDLEWARE = [ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', ] REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'common.exception.handle_exception.handle_exception', 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication'] } STATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist'))] STATIC_ROOT = os.path.join(BASE_DIR.parent, 'static') ROOT_URLCONF = 'maxkb.urls' APPS_DIR = os.path.join(PROJECT_DIR, 'apps') TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/admin"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, {"NAME": "CHAT", 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/chat"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, {"NAME": "DOC", 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/drf_spectacular_sidecar"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] SPECTACULAR_SETTINGS = { 'TITLE': 'MaxKB API', 'DESCRIPTION': _('Intelligent customer service platform'), 'VERSION': 'v2', 'SERVE_INCLUDE_SCHEMA': False, # OTHER SETTINGS 'SWAGGER_UI_DIST': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist', # shorthand to use the sidecar instead 'SWAGGER_UI_FAVICON_HREF': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist/favicon-32x32.png', 'REDOC_DIST': f'{CONFIG.get_admin_path()}/api-doc/redoc', 'SECURITY_DEFINITIONS': { 'Bearer': { 'type': 'apiKey', 'name': 'AUTHORIZATION', 'in': 'header', } } } WSGI_APPLICATION = 'maxkb.wsgi.application' # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = {'default': CONFIG.get_db_setting()} CACHES = CONFIG.get_cache_setting() # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ LANGUAGE_CODE = CONFIG.get("LANGUAGE_CODE") TIME_ZONE = CONFIG.get_time_zone() USE_I18N = True USE_TZ = True # 文件上传配置 DATA_UPLOAD_MAX_NUMBER_FILES = 1000 # 支持的语言 LANGUAGES = [ ('en', 'English'), ('zh', '中文简体'), ('zh-hant', '中文繁体') ] # 翻译文件路径 LOCALE_PATHS = [ os.path.join(BASE_DIR.parent, 'locales') ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' edition = 'CE' if os.environ.get('MAXKB_REDIS_SENTINEL_SENTINELS') is not None: DJANGO_REDIS_CONNECTION_FACTORY = "django_redis.pool.SentinelConnectionFactory" ================================================ FILE: apps/maxkb/settings/base/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/5 14:53 @desc: """ from pathlib import Path from ...const import CONFIG, PROJECT_DIR import os from django.utils.translation import gettext_lazy as _ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = CONFIG.get("SECRET_KEY") or 'django-insecure-zm^1_^i5)3gp^&0io6zg72&z!a*d=9kf9o2%uft+27l)+t(#3e' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = CONFIG.get_debug() ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'drf_spectacular', 'drf_spectacular_sidecar', 'users.apps.UsersConfig', 'tools.apps.ToolConfig', 'knowledge', 'common', 'system_manage', 'models_provider', 'django_celery_beat', 'application', 'chat', 'oss', 'trigger', 'django_apscheduler', ] MIDDLEWARE = [ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'common.middleware.gzip.GZipMiddleware', 'common.middleware.chat_headers_middleware.ChatHeadersMiddleware', 'common.middleware.cross_domain_middleware.CrossDomainMiddleware', 'common.middleware.doc_headers_middleware.DocHeadersMiddleware', ] REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'common.exception.handle_exception.handle_exception', 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication'] } STATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist'))] STATIC_ROOT = os.path.join(BASE_DIR.parent, 'static') ROOT_URLCONF = 'maxkb.urls' APPS_DIR = os.path.join(PROJECT_DIR, 'apps') TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/admin"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, {"NAME": "CHAT", 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/chat"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, {"NAME": "DOC", 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ["apps/static/drf_spectacular_sidecar"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] SPECTACULAR_SETTINGS = { 'TITLE': 'MaxKB API', 'DESCRIPTION': _('Intelligent customer service platform'), 'VERSION': 'v2', 'SERVE_INCLUDE_SCHEMA': False, # OTHER SETTINGS 'SWAGGER_UI_DIST': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist', # shorthand to use the sidecar instead 'SWAGGER_UI_FAVICON_HREF': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist/favicon-32x32.png', 'REDOC_DIST': f'{CONFIG.get_admin_path()}/api-doc/redoc', 'SECURITY_DEFINITIONS': { 'Bearer': { 'type': 'apiKey', 'name': 'AUTHORIZATION', 'in': 'header', } } } WSGI_APPLICATION = 'maxkb.wsgi.application' # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = {'default': CONFIG.get_db_setting()} CACHES = CONFIG.get_cache_setting() # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ LANGUAGE_CODE = CONFIG.get("LANGUAGE_CODE") TIME_ZONE = CONFIG.get_time_zone() USE_I18N = True USE_TZ = True # 文件上传配置 DATA_UPLOAD_MAX_NUMBER_FILES = 1000 # 支持的语言 LANGUAGES = [ ('en', 'English'), ('zh', '中文简体'), ('zh-hant', '中文繁体') ] # 翻译文件路径 LOCALE_PATHS = [ os.path.join(BASE_DIR.parent, 'locales') ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' edition = 'CE' if os.environ.get('MAXKB_REDIS_SENTINEL_SENTINELS') is not None: DJANGO_REDIS_CONNECTION_FACTORY = "django_redis.pool.SentinelConnectionFactory" ================================================ FILE: apps/maxkb/settings/lib.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: lib.py @date:2024/8/16 17:12 @desc: """ import os from redis.sentinel import Sentinel from maxkb.const import CONFIG, PROJECT_DIR, LOG_DIR # celery相关配置 celery_data_dir = os.path.join(PROJECT_DIR, 'data', 'celery_task') if not os.path.exists(celery_data_dir) or not os.path.isdir(celery_data_dir): os.makedirs(celery_data_dir, 0o700, exist_ok=True) os.chmod(os.path.dirname(celery_data_dir), 0o700) # Celery using redis as broker redis_celery_once_db = CONFIG.get("REDIS_DB") redis_celery_db = CONFIG.get('REDIS_DB') CELERY_BROKER_URL_FORMAT = '%(protocol)s://:%(password)s@%(host)s:%(port)s/%(db)s' if CONFIG.get('REDIS_SENTINEL_MASTER') and CONFIG.get('REDIS_SENTINEL_SENTINELS'): sentinels_str = CONFIG.get('REDIS_SENTINEL_SENTINELS') sentinels = [ (host.strip(), int(port)) for hostport in sentinels_str.split(',') for host, port in [hostport.strip().split(':')] ] CELERY_BROKER_URL = ';'.join([CELERY_BROKER_URL_FORMAT % { 'protocol': 'sentinel', 'password': CONFIG.get('REDIS_PASSWORD'), 'host': item[0], 'port': item[1], 'db': redis_celery_db } for item in sentinels]) SENTINEL_OPTIONS = { 'master_name': CONFIG.get('REDIS_SENTINEL_MASTER'), } CELERY_BROKER_TRANSPORT_OPTIONS = CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = SENTINEL_OPTIONS # celery-once 哨兵模式配置 sentinel = Sentinel( sentinels, socket_timeout=5, password=CONFIG.get('REDIS_SENTINEL_PASSWORD', CONFIG.get('REDIS_PASSWORD')) ) master_host, master_port = sentinel.discover_master(CONFIG.get('REDIS_SENTINEL_MASTER')) celery_once_settings = { 'url': f"redis://:{CONFIG.get('REDIS_PASSWORD')}@{master_host}:{master_port}/{redis_celery_once_db}", 'master_name': CONFIG.get('REDIS_SENTINEL_MASTER'), 'password': CONFIG.get('REDIS_PASSWORD'), 'db': redis_celery_once_db, } else: CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % { 'protocol': 'redis', 'password': CONFIG.get('REDIS_PASSWORD'), 'host': CONFIG.get('REDIS_HOST'), 'port': CONFIG.get('REDIS_PORT'), 'db': redis_celery_db } # celery-once 常规模式配置 celery_once_settings = { 'url': CELERY_BROKER_URL_FORMAT % { 'protocol': 'redis', 'password': CONFIG.get('REDIS_PASSWORD'), 'host': CONFIG.get('REDIS_HOST'), 'port': CONFIG.get('REDIS_PORT'), 'db': redis_celery_once_db, } } CELERY_result_backend = CELERY_BROKER_URL CELERY_timezone = CONFIG.get_time_zone() CELERY_ENABLE_UTC = False CELERY_task_serializer = 'hmac_signed_serializer' CELERY_result_serializer = 'hmac_signed_serializer' CELERY_accept_content = ['json', 'hmac_signed_serializer'] CELERY_RESULT_EXPIRES = 600 CELERY_WORKER_TASK_LOG_FORMAT = '%(asctime).19s %(message)s' CELERY_WORKER_LOG_FORMAT = '%(asctime).19s %(message)s' CELERY_TASK_EAGER_PROPAGATES = True CELERY_WORKER_REDIRECT_STDOUTS = True CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO" CELERY_TASK_SOFT_TIME_LIMIT = 3600 CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True # celery-once 配置 celery_once_settings['default_timeout'] = 3600 # 锁的默认超时时间(秒) CELERY_ONCE = { 'backend': 'celery_once.backends.Redis', 'settings': celery_once_settings } CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True CELERY_LOG_DIR = os.path.join(LOG_DIR, 'celery') ================================================ FILE: apps/maxkb/settings/logging.py ================================================ # -*- coding: utf-8 -*- # import os from ..const import PROJECT_DIR, CONFIG, LOG_DIR MAX_KB_LOG_FILE = os.path.join(LOG_DIR, 'maxkb.log') DRF_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'drf_exception.log') UNEXPECTED_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'unexpected_exception.log') LOG_LEVEL = CONFIG.get_log_level() LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, 'main': { 'datefmt': '%Y-%m-%d %H:%M:%S', 'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s', }, 'exception': { 'datefmt': '%Y-%m-%d %H:%M:%S', 'format': '\n%(asctime)s [%(levelname)s] %(message)s', }, 'simple': { 'format': '%(levelname)s %(message)s' }, 'syslog': { 'format': 'maxkb: %(message)s' }, 'msg': { 'format': '%(message)s' } }, 'handlers': { 'null': { 'level': 'DEBUG', 'class': 'logging.NullHandler', }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'main' }, 'file': { 'encoding': 'utf8', 'level': 'DEBUG', 'class': 'common.utils.logger.DailyTimedRotatingFileHandler', 'when': 'midnight', 'interval': 1, 'backupCount': 7, 'formatter': 'main', 'filename': MAX_KB_LOG_FILE, }, 'drf_exception': { 'encoding': 'utf8', 'level': 'DEBUG', 'class': 'common.utils.logger.DailyTimedRotatingFileHandler', 'formatter': 'exception', 'when': 'midnight', 'interval': 1, 'backupCount': 7, 'filename': DRF_EXCEPTION_LOG_FILE, }, 'unexpected_exception': { 'encoding': 'utf8', 'level': 'DEBUG', 'class': 'common.utils.logger.DailyTimedRotatingFileHandler', 'when': 'midnight', 'interval': 1, 'backupCount': 7, 'formatter': 'exception', 'filename': UNEXPECTED_EXCEPTION_LOG_FILE, }, 'syslog': { 'level': 'INFO', 'class': 'logging.NullHandler', 'formatter': 'syslog' }, }, 'loggers': { 'django': { 'handlers': ['null'], 'propagate': False, 'level': LOG_LEVEL, }, 'django.request': { 'handlers': ['console', 'file', 'syslog'], 'level': LOG_LEVEL, 'propagate': False, }, 'sqlalchemy': { 'handlers': ['console', 'file', 'syslog'], 'level': "ERROR", 'propagate': False, }, 'django.db.backends': { 'handlers': ['console', 'file', 'syslog'], 'propagate': False, 'level': LOG_LEVEL, }, 'django.server': { 'handlers': ['console', 'file', 'syslog'], 'level': 'ERROR', 'propagate': False, }, 'max_kb': { 'handlers': ['console', 'file'], 'level': LOG_LEVEL, 'propagate': False, }, 'common.event': { 'handlers': ['console', 'file'], 'level': "DEBUG", 'propagate': False, }, } } SYSLOG_ENABLE = CONFIG.SYSLOG_ENABLE if not os.path.isdir(LOG_DIR): os.makedirs(LOG_DIR, mode=0o700, exist_ok=True) ================================================ FILE: apps/maxkb/settings/mem.py ================================================ # coding=utf-8 import os import gc import threading from maxkb.const import CONFIG from common.utils.logger import maxkb_logger import random import psutil CURRENT_PID=os.getpid() # 1 hour GC_INTERVAL = 3600 def enable_force_gc(): before = int(psutil.Process(CURRENT_PID).memory_info().rss / 1024 / 1024) collected = gc.collect() try: import ctypes ctypes.CDLL("libc.so.6").malloc_trim(0) except Exception: pass after = int(psutil.Process(CURRENT_PID).memory_info().rss / 1024 / 1024) maxkb_logger.debug(f"(PID: {CURRENT_PID}) Forced GC ({collected} objects and {before - after} MB recycled)") t = threading.Timer(GC_INTERVAL - random.randint(0, 900), enable_force_gc) t.daemon = True t.start() if CONFIG.get("ENABLE_FORCE_GC", '1') == "1": maxkb_logger.info(f"(PID: {CURRENT_PID}) Forced GC enabled") enable_force_gc() ================================================ FILE: apps/maxkb/urls/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB-xpack @Author:虎虎 @file: __init__.py.py @date:2025/11/5 14:45 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/maxkb/urls/model.py ================================================ """ URL configuration for maxkb project. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.urls import path, include from maxkb.const import CONFIG admin_api_prefix = CONFIG.get_admin_path()[1:] + '/api/' admin_ui_prefix = CONFIG.get_admin_path() chat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/' chat_ui_prefix = CONFIG.get_chat_path() urlpatterns = [ path(admin_api_prefix, include("local_model.urls")), ] ================================================ FILE: apps/maxkb/urls/web.py ================================================ """ URL configuration for maxkb project. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ import os from pathlib import Path from django.http import HttpResponse, HttpResponseRedirect from django.urls import path, re_path, include from django.views import static from rest_framework import status from chat.urls import urlpatterns as chat_urlpatterns from common.init.init_doc import init_doc from common.result import Result from maxkb import settings from maxkb.conf import PROJECT_DIR from maxkb.const import CONFIG admin_api_prefix = CONFIG.get_admin_path()[1:] + '/api/' admin_ui_prefix = CONFIG.get_admin_path() chat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/' chat_ui_prefix = CONFIG.get_chat_path() urlpatterns = [ path(admin_api_prefix, include("users.urls")), path(admin_api_prefix, include("tools.urls")), path(admin_api_prefix, include("models_provider.urls")), path(admin_api_prefix, include("folders.urls")), path(admin_api_prefix, include("knowledge.urls")), path(admin_api_prefix, include("system_manage.urls")), path(admin_api_prefix, include("application.urls")), path(admin_api_prefix, include("trigger.urls")), path(admin_api_prefix, include("oss.urls")), path(chat_api_prefix, include("oss.urls")), path(chat_api_prefix, include("chat.urls")), path(f'{admin_ui_prefix[1:]}/', include('oss.retrieval_urls')), path(f'{chat_ui_prefix[1:]}/', include('oss.retrieval_urls')), ] init_doc(urlpatterns, chat_urlpatterns) def pro(): urlpatterns.append( re_path(rf'^{CONFIG.get_admin_path()[1:]}/api-doc/(?P.*)$', static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "drf_spectacular_sidecar")}, name='doc'), ) urlpatterns.append( re_path(rf'^{CONFIG.get_chat_path()[1:]}/api-doc/(?P.*)$', static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "drf_spectacular_sidecar")}, name='doc_chat'), ) # 暴露ui静态资源 urlpatterns.append( re_path(rf"^{CONFIG.get_admin_path()[1:]}/(?P.*)$", static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "admin")}, name='admin'), ) # 暴露ui静态资源 urlpatterns.append( re_path(rf'^{CONFIG.get_chat_path()[1:]}/(?P.*)$', static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "chat")}, name='chat'), ) if not settings.DEBUG: pro() def get_index_html(index_path): file = open(index_path, "r", encoding='utf-8') content = file.read() file.close() return content def get_all_files(directory): base_path = Path(directory) file_paths = [ '/' + str(file.relative_to(base_path)).replace('\\', '/') for file in base_path.rglob('*') if file.is_file() ] return sorted(file_paths, key=len, reverse=True) static_dict = { chat_ui_prefix: get_all_files(os.path.join(PROJECT_DIR, 'apps', "static", 'chat')), admin_ui_prefix: get_all_files(os.path.join(PROJECT_DIR, 'apps', "static", 'admin')) } def page_not_found(request, exception): """ 页面不存在处理 """ if request.path.startswith(admin_ui_prefix + '/api/'): return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND") if request.path.startswith(chat_ui_prefix + '/api/'): return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND") if request.path.startswith(chat_ui_prefix): in_ = [url for url in static_dict.get(chat_ui_prefix) if request.path.endswith(url)] if len(in_) > 0: a = chat_ui_prefix + in_[0] return HttpResponseRedirect(a) index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'chat', 'index.html') content = get_index_html(index_path) content = content.replace("prefix: '/chat'", f"prefix: '{CONFIG.get_chat_path()}'") if not os.path.exists(index_path): return HttpResponse("页面不存在", status=404) return HttpResponse(content, status=200) elif request.path.startswith(admin_ui_prefix): in_ = [url for url in static_dict.get(admin_ui_prefix) if request.path.endswith(url)] if len(in_) > 0: a = admin_ui_prefix + in_[0] return HttpResponseRedirect(a) index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'admin', 'index.html') if not os.path.exists(index_path): return HttpResponse("页面不存在", status=404) content = get_index_html(index_path) content = content.replace("prefix: '/admin'", f"prefix: '{CONFIG.get_admin_path()}'").replace( "chatPrefix: '/chat'", f"chatPrefix: '{CONFIG.get_chat_path()}'") return HttpResponse(content, status=200) else: return HttpResponseRedirect(admin_ui_prefix + '/') handler404 = page_not_found ================================================ FILE: apps/maxkb/wsgi/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/5 15:14 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/maxkb/wsgi/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py @date:2025/11/5 15:14 @desc: """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings') application = get_wsgi_application() ================================================ FILE: apps/maxkb/wsgi/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/5 15:14 @desc: """ import builtins import os import sys from django.core.wsgi import get_wsgi_application class TorchBlocker: def __init__(self): self.original_import = builtins.__import__ def __call__(self, name, *args, **kwargs): if len([True for i in ['torch'] if i in name.lower()]) > 0: import types return types.ModuleType(name) else: return self.original_import(name, *args, **kwargs) # 安装导入拦截器 builtins.__import__ = TorchBlocker() os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings') os.environ['TIKTOKEN_CACHE_DIR'] = '/opt/maxkb-app/model/tokenizer/openai-tiktoken-cl100k-base' application = get_wsgi_application() def post_handler(): from common.database_model_manage.database_model_manage import DatabaseModelManage from common import event from common.init import init_template event.run() DatabaseModelManage.init() init_template.run() # 启动后处理函数 post_handler() ================================================ FILE: apps/models_provider/__init__.py ================================================ ================================================ FILE: apps/models_provider/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/models_provider/api/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/models_provider/api/model.py ================================================ # coding=utf-8 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, DefaultResultSerializer from models_provider.serializers.model_serializer import ModelModelSerializer, ModelCreateRequest from django.utils.translation import gettext_lazy as _ class ModelCreateResponse(ResultSerializer): def get_data(self): return ModelModelSerializer() class ModelListResponse(APIMixin): @staticmethod def get_response(): class ModelListResult(ResultSerializer): def get_data(self): return ModelModelSerializer(many=True) return ModelListResult @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description=_("workspace id"), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, ), OpenApiParameter( name="name", description=_("model name"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, ), OpenApiParameter( name="model_type", description=_("model type"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, ), OpenApiParameter( name="model_name", description=_("base model"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, ), OpenApiParameter( name="provider", description=_("provider"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, ), OpenApiParameter( name="create_user", description=_("create user"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, ) ] class ModelCreateAPI(APIMixin): @staticmethod def get_request(): return ModelCreateRequest @staticmethod def get_response(): return ModelCreateResponse @classmethod def get_parameters(cls): return [OpenApiParameter( name="workspace_id", description=_("workspace id"), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] class GetModelApi(APIMixin): @staticmethod def get_query_params_api(): return [OpenApiParameter( name="workspace_id", description=_("workspace id"), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, ), OpenApiParameter( name="model_id", description=_("model id"), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, ) ] @staticmethod def get_request(): return [] @staticmethod def get_response(): return ModelCreateResponse class ModelEditApi(APIMixin): @staticmethod def get_request(): return ModelCreateRequest @staticmethod def get_response(): return ModelCreateResponse class DefaultModelResponse(APIMixin): @staticmethod def get_response(): return DefaultResultSerializer() ================================================ FILE: apps/models_provider/api/provide.py ================================================ # coding=utf-8 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from rest_framework import serializers from django.utils.translation import gettext_lazy as _ class ProvideResponse(ResultSerializer): def get_data(self): return ProvideSerializer() class ProvideSerializer(serializers.Serializer): name = serializers.CharField(required=True, max_length=64, label=_("model name")) provider = serializers.CharField(required=True, label=_("provider")) icon = serializers.CharField(required=True, label=_("icon")) class ProvideListSerializer(serializers.Serializer): key = serializers.CharField(required=True, max_length=64, label=_("model name")) value = serializers.CharField(required=True, label=_("value")) class ModelListSerializer(serializers.Serializer): name = serializers.CharField(required=True, label=_("model name")) model_type = serializers.CharField(required=True, label=_("model type")) desc = serializers.CharField(required=True, label=_("model name")) class ModelParamsFormSerializer(serializers.Serializer): input_type = serializers.CharField(required=False, label=_("input type")) label = serializers.CharField(required=False, label=_("label")) text_field = serializers.CharField(required=False, label=_("text field")) value_field = serializers.CharField(required=False, label=_("value field")) provider = serializers.CharField(required=False, label=_("provider")) method = serializers.CharField(required=False, label=_("method")) required = serializers.BooleanField(required=False, label=_("required")) default_value = serializers.CharField(required=False, label=_("default value")) relation_show_field_dict = serializers.DictField(required=False, label=_("relation show field dict")) relation_trigger_field_dict = serializers.DictField(required=False, label=_("relation trigger field dict")) trigger_type = serializers.CharField(required=False, label=_("trigger type")) attrs = serializers.DictField(required=False, label=_("attrs")) props_info = serializers.DictField(required=False, label=_("props info")) class ModelParamsFormResponse(ResultSerializer): def get_data(self): return ModelParamsFormSerializer(many=True) class ModelListResponse(ResultSerializer): def get_data(self): return ModelListSerializer(many=True) class ProvideListResponse(ResultSerializer): def get_data(self): return ProvideListSerializer(many=True) class ProvideApi(APIMixin): class ModelParamsForm(APIMixin): @staticmethod def get_query_params_api(): return [OpenApiParameter( name="model_type", description=_("model type"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=True, ), OpenApiParameter( name="provider", description=_("provider"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=True, ), OpenApiParameter( name="model_name", description=_("model name"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=True, ) ] @staticmethod def get_response(): return ModelParamsFormResponse class ModelList(APIMixin): @staticmethod def get_query_params_api(): return [OpenApiParameter( name="model_type", description=_("model type"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=True, ), OpenApiParameter( name="provider", description=_("provider"), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=True, ) ] @staticmethod def get_response(): return ModelListResponse @staticmethod def get_response(): return ProvideResponse class ModelTypeList(APIMixin): @staticmethod def get_query_params_api(): return [OpenApiParameter( # 参数的名称是done name="provider", # 对参数的备注 description=_("provider"), # 指定参数的类型 type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, # 指定必须给 required=True, )] @staticmethod def get_response(): return ProvideListResponse ================================================ FILE: apps/models_provider/apps.py ================================================ from django.apps import AppConfig class ModelsProviderConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'models_provider' ================================================ FILE: apps/models_provider/base_model_provider.py ================================================ # coding=utf-8 from abc import ABC, abstractmethod from enum import Enum from functools import reduce from typing import Dict, Iterator, Type, List from pydantic import BaseModel from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ from common.utils.common import encryption class DownModelChunkStatus(Enum): success = "success" error = "error" pulling = "pulling" unknown = 'unknown' class ValidCode(Enum): valid_error = 500 model_not_fount = 404 class DownModelChunk: def __init__(self, status: DownModelChunkStatus, digest: str, progress: int, details: str, index: int): self.details = details self.status = status self.digest = digest self.progress = progress self.index = index def to_dict(self): return { "details": self.details, "status": self.status.value, "digest": self.digest, "progress": self.progress, "index": self.index } class IModelProvider(ABC): @abstractmethod def get_model_info_manage(self): pass @abstractmethod def get_model_provide_info(self): pass def get_model_type_list(self): return self.get_model_info_manage().get_model_type_list() def get_model_list(self, model_type): if model_type is None: raise AppApiException(500, _('Model type cannot be empty')) return self.get_model_info_manage().get_model_list_by_model_type(model_type) def get_model_credential(self, model_type, model_name): model_info = self.get_model_info_manage().get_model_info(model_type, model_name) model_credential = model_info.model_credential if model_type == 'TTI' and model_name.startswith(('qwen', 'wan2.6', 'wan')): if hasattr(model_credential, 'api_base'): api_base = model_credential.api_base if hasattr(api_base, 'default_value') and not api_base.default_value: api_base.default_value = 'https://dashscope.aliyuncs.com/api/v1' return model_credential def get_model_params(self, model_type, model_name): model_info = self.get_model_info_manage().get_model_info(model_type, model_name) return model_info.model_credential def is_valid_credential(self, model_type, model_name, model_credential: Dict[str, object], model_params: Dict[str, object], raise_exception=False): model_info = self.get_model_info_manage().get_model_info(model_type, model_name) return model_info.model_credential.is_valid(model_type, model_name, model_credential, model_params, self, raise_exception=raise_exception) def get_model(self, model_type, model_name, model_credential: Dict[str, object], **model_kwargs) -> BaseModel: model_info = self.get_model_info_manage().get_model_info(model_type, model_name) return model_info.model_class.new_instance(model_type, model_name, model_credential, **model_kwargs) def get_dialogue_number(self): return 3 def down_model(self, model_type: str, model_name, model_credential: Dict[str, object]) -> Iterator[DownModelChunk]: raise AppApiException(500, _('The current platform does not support downloading models')) class MaxKBBaseModel(ABC): @staticmethod @abstractmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): pass @staticmethod def is_cache_model(): return True @staticmethod def filter_optional_params(model_kwargs): optional_params = {} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming', 'show_ref_label', 'stream']: if key == 'extra_body' and isinstance(value, dict): optional_params = {**optional_params, **value} else: optional_params[key] = value return optional_params class BaseModelCredential(ABC): @abstractmethod def is_valid(self, model_type: str, model_name, model: Dict[str, object], model_params, provider, raise_exception=True): pass @abstractmethod def encryption_dict(self, model_info: Dict[str, object]): """ :param model_info: 模型数据 :return: 加密后数据 """ pass def get_model_params_setting_form(self, model_name): """ 模型参数设置表单 :return: """ pass @staticmethod def encryption(message: str): """ 加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890 :param message: :return: """ return encryption(message) class ModelTypeConst(Enum): LLM = {'code': 'LLM', 'message': _('LLM')} EMBEDDING = {'code': 'EMBEDDING', 'message': _('Embedding Model')} STT = {'code': 'STT', 'message': _('Speech2Text')} TTS = {'code': 'TTS', 'message': _('TTS')} IMAGE = {'code': 'IMAGE', 'message': _('Vision Model')} TTI = {'code': 'TTI', 'message': _('Image Generation')} RERANKER = {'code': 'RERANKER', 'message': _('Rerank')} # 文生视频 图生视频 TTV = {'code': 'TTV', 'message': _('Text to Video')} ITV = {'code': 'ITV', 'message': _('Image to Video')} class ModelInfo: def __init__(self, name: str, desc: str, model_type: ModelTypeConst, model_credential: BaseModelCredential, model_class: Type[MaxKBBaseModel], **keywords): self.name = name self.desc = desc self.model_type = model_type.name self.model_credential = model_credential self.model_class = model_class if keywords is not None: for key in keywords.keys(): self.__setattr__(key, keywords.get(key)) def get_name(self): """ 获取模型名称 :return: 模型名称 """ return self.name def get_desc(self): """ 获取模型描述 :return: 模型描述 """ return self.desc def get_model_type(self): return self.model_type def get_model_class(self): return self.model_class def to_dict(self): return reduce(lambda x, y: {**x, **y}, [{attr: self.__getattribute__(attr)} for attr in vars(self) if not attr.startswith("__") and not attr == 'model_credential' and not attr == 'model_class'], {}) class ModelInfoManage: def __init__(self): self.model_dict = {} self.model_list = [] self.default_model_list = [] self.default_model_dict = {} def append_model_info(self, model_info: ModelInfo): self.model_list.append(model_info) model_type_dict = self.model_dict.get(model_info.model_type) if model_type_dict is None: self.model_dict[model_info.model_type] = {model_info.name: model_info} else: model_type_dict[model_info.name] = model_info def append_default_model_info(self, model_info: ModelInfo): self.default_model_list.append(model_info) self.default_model_dict[model_info.model_type] = model_info def get_model_list(self): return [model.to_dict() for model in self.model_list] def get_model_list_by_model_type(self, model_type): return [model.to_dict() for model in self.model_list if model.model_type == model_type] def get_model_type_list(self): return [{'key': _type.value.get('message'), 'value': _type.value.get('code')} for _type in ModelTypeConst if len([model for model in self.model_list if model.model_type == _type.name]) > 0] def get_model_info(self, model_type, model_name) -> ModelInfo: model_info = self.model_dict.get(model_type, {}).get(model_name, self.default_model_dict.get(model_type)) if model_info is None: raise AppApiException(500, _('The model does not support')) return model_info class builder: def __init__(self): self.modelInfoManage = ModelInfoManage() def append_model_info(self, model_info: ModelInfo): self.modelInfoManage.append_model_info(model_info) return self def append_model_info_list(self, model_info_list: List[ModelInfo]): for model_info in model_info_list: self.modelInfoManage.append_model_info(model_info) return self def append_default_model_info(self, model_info: ModelInfo): self.modelInfoManage.append_default_model_info(model_info) return self def build(self): return self.modelInfoManage class ModelProvideInfo: def __init__(self, provider: str, name: str, icon: str): self.provider = provider self.name = name self.icon = icon def to_dict(self): return reduce(lambda x, y: {**x, **y}, [{attr: self.__getattribute__(attr)} for attr in vars(self) if not attr.startswith("__")], {}) ================================================ FILE: apps/models_provider/base_ttv.py ================================================ # coding=utf-8 from abc import abstractmethod from pydantic import BaseModel class BaseGenerationVideo(BaseModel): @abstractmethod def check_auth(self): pass @abstractmethod def generate_video(self, prompt: str, negative_prompt: str = None, first_frame_url=None, last_frame_url=None): pass ================================================ FILE: apps/models_provider/constants/__init__.py ================================================ ================================================ FILE: apps/models_provider/constants/model_provider_constants.py ================================================ # coding=utf-8 from enum import Enum from models_provider.impl.aliyun_bai_lian_model_provider.aliyun_bai_lian_model_provider import \ AliyunBaiLianModelProvider from models_provider.impl.anthropic_model_provider.anthropic_model_provider import AnthropicModelProvider from models_provider.impl.aws_bedrock_model_provider.aws_bedrock_model_provider import BedrockModelProvider from models_provider.impl.azure_model_provider.azure_model_provider import AzureModelProvider from models_provider.impl.deepseek_model_provider.deepseek_model_provider import DeepSeekModelProvider from models_provider.impl.docker_ai_model_provider.docker_ai_model_provider import DockerModelProvider from models_provider.impl.gemini_model_provider.gemini_model_provider import GeminiModelProvider from models_provider.impl.kimi_model_provider.kimi_model_provider import KimiModelProvider from models_provider.impl.local_model_provider.local_model_provider import LocalModelProvider from models_provider.impl.ollama_model_provider.ollama_model_provider import OllamaModelProvider from models_provider.impl.openai_model_provider.openai_model_provider import OpenAIModelProvider from models_provider.impl.regolo_model_provider.regolo_model_provider import RegoloModelProvider from models_provider.impl.siliconCloud_model_provider.siliconCloud_model_provider import SiliconCloudModelProvider from models_provider.impl.tencent_cloud_model_provider.tencent_cloud_model_provider import TencentCloudModelProvider from models_provider.impl.tencent_model_provider.tencent_model_provider import TencentModelProvider from models_provider.impl.vllm_model_provider.vllm_model_provider import VllmModelProvider from models_provider.impl.volcanic_engine_model_provider.volcanic_engine_model_provider import \ VolcanicEngineModelProvider from models_provider.impl.wenxin_model_provider.wenxin_model_provider import WenxinModelProvider from models_provider.impl.xf_model_provider.xf_model_provider import XunFeiModelProvider from models_provider.impl.xinference_model_provider.xinference_model_provider import XinferenceModelProvider from models_provider.impl.zhipu_model_provider.zhipu_model_provider import ZhiPuModelProvider class ModelProvideConstants(Enum): model_azure_provider = AzureModelProvider() model_wenxin_provider = WenxinModelProvider() model_ollama_provider = OllamaModelProvider() model_openai_provider = OpenAIModelProvider() model_docker_ai_provider = DockerModelProvider() model_kimi_provider = KimiModelProvider() model_zhipu_provider = ZhiPuModelProvider() model_xf_provider = XunFeiModelProvider() model_deepseek_provider = DeepSeekModelProvider() model_gemini_provider = GeminiModelProvider() model_volcanic_engine_provider = VolcanicEngineModelProvider() model_tencent_provider = TencentModelProvider() model_tencent_cloud_provider = TencentCloudModelProvider() model_aws_bedrock_provider = BedrockModelProvider() model_local_provider = LocalModelProvider() model_xinference_provider = XinferenceModelProvider() model_vllm_provider = VllmModelProvider() aliyun_bai_lian_model_provider = AliyunBaiLianModelProvider() model_anthropic_provider = AnthropicModelProvider() model_siliconCloud_provider = SiliconCloudModelProvider() model_regolo_provider = RegoloModelProvider() ================================================ FILE: apps/models_provider/impl/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/9/9 17:42 @desc: """ ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: aliyun_bai_lian_model_provider.py @date:2024/9/9 17:43 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \ ModelInfoManage from models_provider.impl.aliyun_bai_lian_model_provider.credential.stt.asr_stt import AliyunBaiLianAsrSTTModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.embedding import \ AliyunBaiLianEmbeddingCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.image import QwenVLModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.itv import ImageToVideoModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.llm import BaiLianLLMModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.stt.omni_stt import AliyunBaiLianOmiSTTModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.reranker import \ AliyunBaiLianRerankerCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.stt import AliyunBaiLianSTTModelCredential, \ AliyunBaiLianDefaultSTTModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.tti import QwenTextToImageModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.tts import AliyunBaiLianTTSModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.credential.ttv import TextToVideoModelCredential from models_provider.impl.aliyun_bai_lian_model_provider.model.stt.asr_stt import AliyunBaiLianAsrSpeechToText from models_provider.impl.aliyun_bai_lian_model_provider.model.embedding import AliyunBaiLianEmbedding from models_provider.impl.aliyun_bai_lian_model_provider.model.image import QwenVLChatModel from models_provider.impl.aliyun_bai_lian_model_provider.model.llm import BaiLianChatModel from models_provider.impl.aliyun_bai_lian_model_provider.model.stt.omni_stt import AliyunBaiLianOmiSpeechToText from models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker from models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianSpeechToText, \ AliyunBaiLianDefaultSpeechToText from models_provider.impl.aliyun_bai_lian_model_provider.model.tti import QwenTextToImageModel from models_provider.impl.aliyun_bai_lian_model_provider.model.tts import AliyunBaiLianTextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _, gettext from models_provider.impl.aliyun_bai_lian_model_provider.model.ttv import GenerationVideoModel aliyun_bai_lian_model_credential = AliyunBaiLianRerankerCredential() aliyun_bai_lian_tts_model_credential = AliyunBaiLianTTSModelCredential() aliyun_bai_lian_stt_model_credential = AliyunBaiLianSTTModelCredential() aliyun_bai_lian_omi_stt_model_credential = AliyunBaiLianOmiSTTModelCredential() aliyun_bai_lian_asr_stt_model_credential = AliyunBaiLianAsrSTTModelCredential() aliyun_bai_lian_default_stt_model_credential = AliyunBaiLianDefaultSTTModelCredential() aliyun_bai_lian_embedding_model_credential = AliyunBaiLianEmbeddingCredential() aliyun_bai_lian_llm_model_credential = BaiLianLLMModelCredential() qwenvl_model_credential = QwenVLModelCredential() qwentti_model_credential = QwenTextToImageModelCredential() aliyun_bai_lian_ttv_model_credential = TextToVideoModelCredential() aliyun_bai_lian_itv_model_credential = ImageToVideoModelCredential() model_info_list = [ModelInfo('gte-rerank-v2', _('With the GTE-Rerank text sorting series model developed by Alibaba Tongyi Lab, developers can integrate high-quality text retrieval and sorting through the LlamaIndex framework.'), ModelTypeConst.RERANKER, aliyun_bai_lian_model_credential, AliyunBaiLianReranker), ModelInfo('paraformer-realtime-v2', _('Chinese (including various dialects such as Cantonese), English, Japanese, and Korean support free switching between multiple languages.'), ModelTypeConst.STT, aliyun_bai_lian_stt_model_credential, AliyunBaiLianSpeechToText), ModelInfo('cosyvoice-v1', _('CosyVoice is based on a new generation of large generative speech models, which can predict emotions, intonation, rhythm, etc. based on context, and has better anthropomorphic effects.'), ModelTypeConst.TTS, aliyun_bai_lian_tts_model_credential, AliyunBaiLianTextToSpeech), ModelInfo('text-embedding-v1', _("Universal text vector is Tongyi Lab's multi-language text unified vector model based on the LLM base. It provides high-level vector services for multiple mainstream languages around the world and helps developers quickly convert text data into high-quality vector data."), ModelTypeConst.EMBEDDING, aliyun_bai_lian_embedding_model_credential, AliyunBaiLianEmbedding), ModelInfo('qwen3-0.6b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-1.7b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-4b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-8b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-14b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-32b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-30b-a3b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen3-235b-a22b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen-turbo', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen-plus', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen-max', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential, BaiLianChatModel), ModelInfo('qwen-omni-turbo', _('The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.'), ModelTypeConst.STT, aliyun_bai_lian_omi_stt_model_credential, AliyunBaiLianOmiSpeechToText), ModelInfo('qwen2.5-omni-7b', _('The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.'), ModelTypeConst.STT, aliyun_bai_lian_omi_stt_model_credential, AliyunBaiLianOmiSpeechToText), ModelInfo('qwen-audio-asr', _('The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition.'), ModelTypeConst.STT, aliyun_bai_lian_asr_stt_model_credential, AliyunBaiLianAsrSpeechToText), ] module_info_vl_list = [ ModelInfo('qwen-vl-max', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel), ModelInfo('qwen-vl-max-0809', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel), ModelInfo('qwen-vl-plus-0809', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel), ] module_info_tti_list = [ ModelInfo('wanx-v1', _('Tongyi Wanxiang - a large image model for text generation, supports bilingual input in Chinese and English, and supports the input of reference pictures for reference content or reference style migration. Key styles include but are not limited to watercolor, oil painting, Chinese painting, sketch, flat illustration, two-dimensional, and 3D. Cartoon.'), ModelTypeConst.TTI, qwentti_model_credential, QwenTextToImageModel), ] model_info_ttv_list = [ ModelInfo('wan2.2-t2v-plus', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential, GenerationVideoModel), ModelInfo('wanx2.1-t2v-turbo', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential, GenerationVideoModel), ModelInfo('wanx2.1-t2v-plus', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential, GenerationVideoModel), ] module_info_itv_list = [ ModelInfo('wan2.2-i2v-flash', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential, GenerationVideoModel), ModelInfo('wan2.2-i2v-plus', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential, GenerationVideoModel), ModelInfo('wanx2.1-i2v-plus', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential, GenerationVideoModel), ModelInfo('wanx2.1-i2v-turbo', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential, GenerationVideoModel), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_model_info_list(module_info_vl_list) .append_default_model_info(module_info_vl_list[0]) .append_model_info_list(module_info_tti_list) .append_default_model_info(module_info_tti_list[0]) .append_default_model_info(model_info_list[1]) .append_default_model_info(model_info_list[2]) .append_default_model_info(ModelInfo('default', _('default'), ModelTypeConst.STT, aliyun_bai_lian_default_stt_model_credential, AliyunBaiLianDefaultSpeechToText)) .append_default_model_info(model_info_list[3]) .append_default_model_info(model_info_list[4]) .append_default_model_info(model_info_list[0]) .append_model_info_list(model_info_ttv_list) .append_default_model_info(model_info_ttv_list[0]) .append_model_info_list(module_info_itv_list) .append_default_model_info(module_info_itv_list[0]) .build() ) class AliyunBaiLianModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='aliyun_bai_lian_model_provider', name=gettext('Alibaba Cloud Bailian'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'aliyun_bai_lian_model_provider', 'icon', 'aliyun_bai_lian_icon_svg'))) ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/16 17:01 @desc: """ from typing import Dict, Any from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.aliyun_bai_lian_model_provider.model.embedding import AliyunBaiLianEmbedding from common.utils.logger import maxkb_logger class BaiLianEmbeddingModelParams(BaseForm): dimensions = forms.SingleSelect( TooltipLabel( _('Dimensions'), _('') ), required=True, default_value=1024, value_field='value', text_field='label', option_list=[ {'label': '1024', 'value': '1024'}, {'label': '768', 'value': '768'}, {'label': '512', 'value': '512'}, ] ) class AliyunBaiLianEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider: Any, raise_exception: bool = False ) -> bool: """ 验证模型凭据是否有效 """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, f"{model_type} Model type is not supported" ) required_keys = ['dashscope_api_key', 'api_base'] missing_keys = [key for key in required_keys if key not in model_credential] if missing_keys: if raise_exception: raise AppApiException( ValidCode.valid_error.value, f"{', '.join(missing_keys)} is required" ) return False try: model: AliyunBaiLianEmbedding = provider.get_model(model_type, model_name, model_credential) model.embed_query(_("Hello")) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, f"Verification failed, please check whether the parameters are correct: {e}" ) return False return True def encryption_dict(self, model: Dict[str, Any]) -> Dict[str, Any]: """ 加密敏感信息 """ api_key = model.get('dashscope_api_key', '') return {**model, 'dashscope_api_key': super().encryption(api_key)} def get_model_params_setting_form(self, model_name): return BaiLianEmbeddingModelParams() api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') dashscope_api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:41 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class QwenModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=1.0, _min=0.1, _max=1.9, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class QwenVLModelCredential(BaseForm, BaseModelCredential): def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, object], model_params: dict, provider, raise_exception: bool = False ) -> bool: model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key', 'api_base'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth(model_credential.get('api_key')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e) ) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return QwenModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py ================================================ # coding=utf-8 from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from common.forms.switch_field import SwitchField from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class QwenModelParams(BaseForm): """ Parameters class for the Qwen Image-to-Video model. Defines fields such as Video size, number of Videos, and style. """ resolution = SingleSelect( TooltipLabel(_('Resolution'), ''), required=True, default_value='480P', option_list=[ {'value': '480P', 'label': '480P'}, {'value': '720P', 'label': '720P'}, {'value': '1080P', 'label': '1080P'}, ], text_field='label', value_field='value' ) watermark = SwitchField( TooltipLabel(_('Watermark'), _('Whether to add watermark')), attrs={"active-value": "true", "inactive-value": "false"}, default_value=False, ) class ImageToVideoModelCredential(BaseForm, BaseModelCredential): """ Credential class for the Qwen Image-to-Video model. Provides validation and encryption for the model credentials. """ api_key = PasswordInputField('API Key', required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'TEXT_TO_Video'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}' ).format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name: str): """ Get the parameter setting form for the specified model. :param model_name: Name of the model. :return: Parameter setting form. """ return QwenModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py ================================================ # coding=utf-8 from typing import Dict from langchain_core.messages import HumanMessage from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class BaiLianLLMModelParams(BaseForm): temperature = forms.SliderField( TooltipLabel( _('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic') ), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2 ) max_tokens = forms.SliderField( TooltipLabel( _('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate.') ), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0 ) class BaiLianLLMModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField(_('API URL'), required=True) api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, object], model_params: dict, provider, raise_exception: bool = False ) -> bool: model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) if model_params.get('stream'): for res in model.stream([HumanMessage(content=gettext('Hello'))]): pass else: model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e) ) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name: str) -> BaiLianLLMModelParams: return BaiLianLLMModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py ================================================ # coding=utf-8 from typing import Dict, Any from django.utils.translation import gettext as _ from langchain_core.documents import Document from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker from common.utils.logger import maxkb_logger class AliyunBaiLianRerankerCredential(BaseForm, BaseModelCredential): """ Credential class for the Aliyun BaiLian Reranker model. Provides validation and encryption for the model credentials. """ dashscope_api_key = PasswordInputField('API Key', required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'RERANKER'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ if model_type != 'RERANKER': raise AppApiException( ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['dashscope_api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('{key} is required').format(key=key) ) return False try: model: AliyunBaiLianReranker = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=_('Hello'))], _('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'dashscope_api_key': super().encryption(model.get('dashscope_api_key', '')) } ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/12/5 15:11 @desc: """ from .stt import AliyunBaiLianSTTModelCredential from .omni_stt import AliyunBaiLianOmiSTTModelCredential from .default_stt import AliyunBaiLianDefaultSTTModelCredential from .asr_stt import AliyunBaiLianAsrSTTModelCredential ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/asr_stt.py ================================================ # coding=utf-8 from typing import Dict, Any from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext as _ from common.utils.logger import maxkb_logger class AliyunBaiLianAsrSTTModelCredential(BaseForm, BaseModelCredential): api_url = forms.TextInputField(_('API URL'), required=True) api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid(self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/default_stt.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: default_stt.py @date:2025/12/5 15:12 @desc: """ from typing import Dict, Any from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from maxkb.settings import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext as _ class AliyunBaiLianDefaultSTTModelCredential(BaseForm, BaseModelCredential): type = forms.SingleSelect(_("API"), required=True, text_field='label', default_value='qwen', provider='', method='', value_field='value', option_list=[ {'label': _('Audio file recognition - Tongyi Qwen'), 'value': 'qwen'}, {'label': _('Qwen-Omni'), 'value': 'omni'}, {'label': _('Real-time speech recognition - Fun-ASR/Paraformer'), 'value': 'other'} ]) api_url = forms.TextInputField(_('API URL'), required=True, relation_show_field_dict={'type': ['qwen', 'omni']}) api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid(self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/omni_stt.py ================================================ # coding=utf-8 from typing import Dict, Any from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext as _ from common.utils.logger import maxkb_logger class AliyunBaiLianOmiSTTModelParams(BaseForm): CueWord = forms.TextInputField( TooltipLabel(_('CueWord'), _('If not passed, the default value is What is this audio saying? Only answer the audio content')), required=True, default_value='这段音频在说什么,只回答音频的内容', ) class AliyunBaiLianOmiSTTModelCredential(BaseForm, BaseModelCredential): api_url = forms.TextInputField(_('API URL'), required=True) api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid(self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name): return AliyunBaiLianOmiSTTModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/stt.py ================================================ # coding=utf-8 from typing import Dict, Any from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AliyunBaiLianSTTModelParams(BaseForm): sample_rate = forms.SliderField( TooltipLabel(_('Sample Rate'), _('If not passed, the default value is 16000')), required=True, default_value=16000, _step=4000, _min=0, _max=20000,precision=0 ) class AliyunBaiLianSTTModelCredential(BaseForm, BaseModelCredential): """ Credential class for the Aliyun BaiLian STT (Speech-to-Text) model. Provides validation and encryption for the model credentials. """ api_key = PasswordInputField("API Key", required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'STT'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential,**model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name: str): """ Get the parameter setting form for the specified model. :param model_name: Name of the model. :return: Parameter setting form (not implemented). """ return AliyunBaiLianSTTModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class QwenModelParams(BaseForm): """ Parameters class for the Qwen Text-to-Image model. Defines fields such as image size, number of images, and style. """ size = SingleSelect( TooltipLabel(_('Image size'), _('Specify the size of the generated image, such as: 1024x1024')), required=True, default_value='1024*1024', option_list=[ {'value': '1024*1024', 'label': '1024*1024'}, {'value': '720*1280', 'label': '720*1280'}, {'value': '768*1152', 'label': '768*1152'}, {'value': '1280*720', 'label': '1280*720'}, ], text_field='label', value_field='value', attrs={'allow-create': True, 'filterable': True} ) n = SliderField( TooltipLabel(_('Number of pictures'), _('Specify the number of generated images')), required=True, default_value=1, _min=1, _max=4, _step=1, precision=0 ) style = SingleSelect( TooltipLabel(_('Style'), _('Specify the style of generated images')), required=True, default_value='', option_list=[ {'value': '', 'label': _('Default value, the image style is randomly output by the model')}, {'value': '', 'label': _('photography')}, {'value': '', 'label': _('Portraits')}, {'value': '<3d cartoon>', 'label': _('3D cartoon')}, {'value': '', 'label': _('animation')}, {'value': '', 'label': _('painting')}, {'value': '', 'label': _('watercolor')}, {'value': '', 'label': _('sketch')}, {'value': '', 'label': _('Chinese painting')}, {'value': '', 'label': _('flat illustration')}, ], text_field='label', value_field='value' ) class QwenTextToImageModelCredential(BaseForm, BaseModelCredential): """ Credential class for the Qwen Text-to-Image model. Provides validation and encryption for the model credentials. """ api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://dashscope.aliyuncs.com/api/v1') api_key = PasswordInputField('API Key', required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'TEXT_TO_IMAGE'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key', 'api_base'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}' ).format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name: str): """ Get the parameter setting form for the specified model. :param model_name: Name of the model. :return: Parameter setting form. """ return QwenModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AliyunBaiLianTTSModelGeneralParams(BaseForm): """ Parameters class for the Aliyun BaiLian TTS (Text-to-Speech) model. Defines fields such as voice and speech rate. """ voice = SingleSelect( TooltipLabel(_('Timbre'), _('Chinese sounds can support mixed scenes of Chinese and English')), required=True, default_value='longxiaochun', text_field='value', value_field='value', option_list=[ {'label': _('Long Xiaochun'), 'value': 'longxiaochun'}, {'label': _('Long Xiaoxia'), 'value': 'longxiaoxia'}, {'label': _('Long Xiaochen'), 'value': 'longxiaocheng'}, {'label': _('Long Xiaobai'), 'value': 'longxiaobai'}, {'label': _('Long Laotie'), 'value': 'longlaotie'}, {'label': _('Long Shu'), 'value': 'longshu'}, {'label': _('Long Shuo'), 'value': 'longshuo'}, {'label': _('Long Jing'), 'value': 'longjing'}, {'label': _('Long Miao'), 'value': 'longmiao'}, {'label': _('Long Yue'), 'value': 'longyue'}, {'label': _('Long Yuan'), 'value': 'longyuan'}, {'label': _('Long Fei'), 'value': 'longfei'}, {'label': _('Long Jielidou'), 'value': 'longjielidou'}, {'label': _('Long Tong'), 'value': 'longtong'}, {'label': _('Long Xiang'), 'value': 'longxiang'}, {'label': 'Stella', 'value': 'loongstella'}, {'label': 'Bella', 'value': 'loongbella'}, {'label': 'longxiaochun_v2', 'value': 'longxiaochun_v2'}, {'label': 'longyingmu_v3', 'value': 'longyingmu_v3'}, ] ) speech_rate = SliderField( TooltipLabel(_('Speaking speed'), _('[0.5, 2], the default is 1, usually one decimal place is enough')), required=True, default_value=1, _min=0.5, _max=2, _step=0.1, precision=1 ) class AliyunBaiLianTTSModelCredential(BaseForm, BaseModelCredential): """ Credential class for the Aliyun BaiLian TTS (Text-to-Speech) model. Provides validation and encryption for the model credentials. """ api_key = PasswordInputField("API Key", required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, object], model_params, provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'TTS'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}' ).format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name: str): """ Get the parameter setting form for the specified model. :param model_name: Name of the model. :return: Parameter setting form. """ return AliyunBaiLianTTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py ================================================ # coding=utf-8 from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from common.forms.switch_field import SwitchField from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class QwenModelParams(BaseForm): """ Parameters class for the Qwen Text-to-Video model. Defines fields such as Video size, number of Videos, and style. """ size = SingleSelect( TooltipLabel(_('Video size'), _('Specify the size of the generated Video, such as: 1024x1024')), required=True, default_value='1280*720', option_list=[ {'value': '832*480', 'label': '832*480'}, {'value': '480*832', 'label': '480*832'}, {'value': '1280*720', 'label': '1280*720'}, {'value': '720*1280', 'label': '720*1280'}, ], text_field='label', value_field='value' ) watermark = SwitchField( TooltipLabel(_('Watermark'), _('Whether to add watermark')), attrs={"active-value": "true", "inactive-value": "false"}, default_value=False, ) class TextToVideoModelCredential(BaseForm, BaseModelCredential): """ Credential class for the Qwen Text-to-Video model. Provides validation and encryption for the model credentials. """ api_key = PasswordInputField('API Key', required=True) def is_valid( self, model_type: str, model_name: str, model_credential: Dict[str, Any], model_params: Dict[str, Any], provider, raise_exception: bool = False ) -> bool: """ Validate the model credentials. :param model_type: Type of the model (e.g., 'TEXT_TO_Video'). :param model_name: Name of the model. :param model_credential: Dictionary containing the model credentials. :param model_params: Parameters for the model. :param provider: Model provider instance. :param raise_exception: Whether to raise an exception on validation failure. :return: Boolean indicating whether the credentials are valid. """ model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): raise AppApiException( ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type) ) required_keys = ['api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext('{key} is required').format(key=key) ) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}' ).format(error=str(e)) ) return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: """ Encrypt sensitive fields in the model dictionary. :param model: Dictionary containing model details. :return: Dictionary with encrypted sensitive fields. """ return { **model, 'api_key': super().encryption(model.get('api_key', '')) } def get_model_params_setting_form(self, model_name: str): """ Get the parameter setting form for the specified model. :param model_name: Name of the model. :return: Parameter setting form. """ return QwenModelParams() ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/16 16:34 @desc: """ from typing import Dict, List from openai import OpenAI from models_provider.base_model_provider import MaxKBBaseModel class AliyunBaiLianEmbedding(MaxKBBaseModel): model_name: str optional_params: dict def __init__(self, api_key, model_name: str, api_base: str, optional_params: dict): self.client = OpenAI(api_key=api_key, base_url=api_base).embeddings self.model_name = model_name self.optional_params = optional_params def is_cache_model(self): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return AliyunBaiLianEmbedding( api_key=model_credential.get('dashscope_api_key'), model_name=model_name, api_base=model_credential.get('api_base') or 'https://dashscope.aliyuncs.com/compatible-mode/v1', optional_params=optional_params ) def embed_query(self, text: str): res = self.embed_documents([text]) return res[0] def embed_documents( self, texts: List[str], chunk_size: int | None = None ) -> List[List[float]]: if len(self.optional_params) > 0: res = self.client.create( input=texts, model=self.model_name, encoding_format="float", **self.optional_params ) else: res = self.client.create(input=texts, model=self.model_name, encoding_format="float") return [e.embedding for e in res.data] ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py ================================================ # coding=utf-8 import json import time from typing import Dict, Optional, Any, Iterator import requests from langchain_core.language_models import LanguageModelInput from langchain_core.messages import BaseMessageChunk, AIMessage from langchain_core.runnables import RunnableConfig from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) chat_tong_yi = QwenVLChatModel( model_name=model_name, openai_api_key=model_credential.get('api_key'), openai_api_base=model_credential.get('api_base') or 'https://dashscope.aliyuncs.com/compatible-mode/v1', # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) return chat_tong_yi def check_auth(self, api_key): return True def get_upload_policy(self, api_key, model_name): """获取文件上传凭证""" url = "https://dashscope.aliyuncs.com/api/v1/uploads" if 'dashscope-us' in self.openai_api_base: url = "https://dashscope-us.aliyuncs.com/api/v1/uploads" elif 'dashscope-intl' in self.openai_api_base: url = "https://dashscope-intl.aliyuncs.com/api/v1/uploads" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } params = { "action": "getPolicy", "model": model_name } response = requests.get(url, headers=headers, params=params) if response.status_code != 200: raise Exception(f"Failed to get upload policy: {response.text}") return response.json()['data'] def upload_file_to_oss(self, policy_data, file_stream, file_name): """将文件流上传到临时存储OSS""" # 构建OSS上传的目标路径 key = f"{policy_data['upload_dir']}/{file_name}" # 构建上传数据 files = { 'OSSAccessKeyId': (None, policy_data['oss_access_key_id']), 'Signature': (None, policy_data['signature']), 'policy': (None, policy_data['policy']), 'x-oss-object-acl': (None, policy_data['x_oss_object_acl']), 'x-oss-forbid-overwrite': (None, policy_data['x_oss_forbid_overwrite']), 'key': (None, key), 'success_action_status': (None, '200'), 'file': (file_name, file_stream) } # 执行上传请求 response = requests.post(policy_data['upload_host'], files=files) if response.status_code != 200: raise Exception(f"Failed to upload file: {response.text}") return f"oss://{key}" def upload_file_and_get_url(self, file_stream, file_name): max_retries = 3 retry_delay = 1 # 初始重试延迟(秒) for attempt in range(max_retries): try: # 1. 获取上传凭证,上传凭证接口有限流,超出限流将导致请求失败 policy_data = self.get_upload_policy(self.openai_api_key.get_secret_value(), self.model_name) # 2. 上传文件到OSS oss_url = self.upload_file_to_oss(policy_data, file_stream, file_name) return oss_url except Exception as e: if attempt < max_retries - 1: # 指数退避策略 time.sleep(retry_delay * (2 ** attempt)) continue else: raise Exception(f"文件上传失败,已重试{max_retries}次: {str(e)}") def stream( self, input: LanguageModelInput, config: Optional[RunnableConfig] = None, *, stop: Optional[list[str]] = None, **kwargs: Any, ) -> Iterator[BaseMessageChunk]: url = f"{self.openai_api_base}/chat/completions" headers = { "Authorization": f"Bearer {self.openai_api_key.get_secret_value()}", "Content-Type": "application/json", "X-DashScope-OssResourceResolve": "enable" } # 遍历input 获取所有的content 构造新的消息体 messages = [] for message in input: if message.type == "human": messages.append({ "role": "user", "content": message.content }) elif message.type == "ai": messages.append({ "role": "assistant", "content": message.content }) elif message.type == "system": messages.append({ "role": "system", "content": message.content }) data = { "model": self.model_name, "messages": messages, **self.extra_body, "stream": True, } # 增加重试机制 max_retries = 3 retry_delay = 1 for attempt in range(max_retries): try: response = requests.post(url, headers=headers, json=data, stream=True, timeout=30) if response.status_code != 200: raise Exception(f"Failed to get response: {response.text}") for line in response.iter_lines(): if line: try: decoded_line = line.decode('utf-8') # 检查是否是有效的SSE数据行 if decoded_line.startswith('data: '): # 提取JSON部分 json_str = decoded_line[6:] # 移除 'data: ' 前缀 # 检查是否是结束标记 if json_str.strip() == '[DONE]': continue # 尝试解析JSON chunk_data = json.loads(json_str) if 'choices' in chunk_data and chunk_data['choices']: delta = chunk_data['choices'][0].get('delta', {}) content = delta.get('content', '') if content: yield AIMessage(content=content) except json.JSONDecodeError: # 忽略无法解析的行 continue except Exception as e: # 处理其他可能的异常 continue break # 成功执行则退出重试循环 except (requests.exceptions.ProxyError, requests.exceptions.ConnectionError) as e: if attempt < max_retries - 1: time.sleep(retry_delay * (2 ** attempt)) # 指数退避 continue else: raise Exception(f"网络连接失败,已重试{max_retries}次: {str(e)}") except Exception as e: if attempt < max_retries - 1: time.sleep(retry_delay * (2 ** attempt)) continue else: raise Exception(f"请求失败,已重试{max_retries}次: {str(e)}") ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/llm.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class BaiLianChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) # if 'qwen-omni-turbo' in model_name or 'qwq' in model_name: # optional_params['streaming'] = True return BaiLianChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), streaming=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: reranker.py.py @date:2024/9/2 16:42 @desc: """ from http import HTTPStatus from typing import Sequence, Optional, Any, Dict import dashscope from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from langchain_core.documents import BaseDocumentCompressor from models_provider.base_model_provider import MaxKBBaseModel class AliyunBaiLianReranker(MaxKBBaseModel, BaseDocumentCompressor): model: Optional[str] api_key: Optional[str] top_n: Optional[int] = 3 # 取前 N 个最相关的结果 @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return AliyunBaiLianReranker(model=model_name, api_key=model_credential.get('dashscope_api_key'), top_n=model_kwargs.get('top_n', 3)) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if not documents: return [] texts = [doc.page_content for doc in documents] resp = dashscope.TextReRank.call( model=self.model, query=query, documents=texts, top_n=self.top_n, api_key=self.api_key, return_documents=True ) if resp.status_code == HTTPStatus.OK: return [ Document( page_content=item.get('document', {}).get('text', ''), metadata={'relevance_score': item.get('relevance_score')} ) for item in resp.output.get('results', []) ] else: return [] ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/__init__.py ================================================ #coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/12/5 15:39 @desc: """ from .asr_stt import AliyunBaiLianAsrSpeechToText from .default_stt import AliyunBaiLianDefaultSpeechToText from .stt import AliyunBaiLianSpeechToText from .omni_stt import AliyunBaiLianOmiSpeechToText ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/asr_stt.py ================================================ import base64 import os.path import traceback from typing import Dict import dashscope from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText class AliyunBaiLianAsrSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_key: str api_url: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') self.api_url = kwargs.get('api_url') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return AliyunBaiLianAsrSpeechToText( model=model_name, api_key=model_credential.get('api_key'), api_url=model_credential.get('api_url'), params=model_kwargs, **model_kwargs ) def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file: self.speech_to_text(audio_file) def speech_to_text(self, audio_file): try: base64_audio = base64.b64encode(audio_file.read()).decode("utf-8") messages = [ { "role": "user", "content": [ {"audio": f"data:audio/mp3;base64,{base64_audio}"}, ] } ] response = dashscope.MultiModalConversation.call( api_key=self.api_key, model=self.model, messages=messages, result_format="message", **self.params ) if response.status_code == 200: text = response["output"]["choices"][0]["message"].content[0]["text"] return text else: raise Exception('Error: ', response.message) except Exception as err: maxkb_logger.error(f":Error: {str(err)}: {traceback.format_exc()}") raise err ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/default_stt.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: default_stt.py @date:2025/12/5 15:40 @desc: """ import os from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText class AliyunBaiLianDefaultSpeechToText(MaxKBBaseModel, BaseSpeechToText): def check_auth(self): pass def speech_to_text(self, audio_file): pass @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): from models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianOmiSpeechToText, \ AliyunBaiLianSpeechToText, AliyunBaiLianAsrSpeechToText stt_type=model_credential.get('type') if stt_type == 'qwen': return AliyunBaiLianAsrSpeechToText( model=model_name, api_key=model_credential.get('api_key'), api_url=model_credential.get('api_url'), params=model_kwargs, **model_kwargs ) elif stt_type == 'omni': return AliyunBaiLianOmiSpeechToText( model=model_name, api_key=model_credential.get('api_key'), api_url=model_credential.get('api_url'), params=model_kwargs, **model_kwargs ) else: return AliyunBaiLianSpeechToText( model=model_name, api_key=model_credential.get('api_key'), params=model_kwargs, **model_kwargs, ) ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/omni_stt.py ================================================ import base64 import os import traceback from typing import Dict from openai import OpenAI from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText class AliyunBaiLianOmiSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_key: str api_url: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') self.api_url = kwargs.get('api_url') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return AliyunBaiLianOmiSpeechToText( model=model_name, api_key=model_credential.get('api_key'), api_url=model_credential.get('api_url') , params= model_kwargs, **model_kwargs ) def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file: self.speech_to_text(audio_file) def speech_to_text(self, audio_file): try: client = OpenAI( # 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:api_key="sk-xxx", api_key=self.api_key, base_url=self.api_url, ) base64_audio = base64.b64encode(audio_file.read()).decode("utf-8") completion = client.chat.completions.create( model=self.model, messages=[ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": f"data:;base64,{base64_audio}", "format": "mp3", }, }, {"type": "text", "text": self.params.get('CueWord') or '这段音频在说什么'}, ], }, ], # 设置输出数据的模态,当前支持两种:["text","audio"]、["text"] modalities=["text"], # stream 必须设置为 True,否则会报错 stream=True, stream_options={"include_usage": True}, extra_body = {'enable_thinking': False, **self.params}, ) result = [] for chunk in completion: if chunk.choices and hasattr(chunk.choices[0].delta, 'content'): content = chunk.choices[0].delta.content result.append(content) return "".join(result) except Exception as err: maxkb_logger.error(f":Error: {str(err)}: {traceback.format_exc()}") raise err ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/stt.py ================================================ import os import tempfile from typing import Dict import dashscope from dashscope.audio.asr import (Recognition) from pydub import AudioSegment from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText class AliyunBaiLianSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return AliyunBaiLianSpeechToText( model=model_name, api_key=model_credential.get('api_key'), params=model_kwargs, **optional_params, ) def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f: self.speech_to_text(f) def speech_to_text(self, audio_file): dashscope.api_key = self.api_key recognition_params = { 'model': self.model, 'format': 'mp3', 'sample_rate': 16000, 'callback': None, **self.params } recognition = Recognition(**recognition_params) with tempfile.NamedTemporaryFile(delete=False) as temp_file: # 将上传的文件保存到临时文件中 temp_file.write(audio_file.read()) # 获取临时文件的路径 temp_file_path = temp_file.name try: audio = AudioSegment.from_file(temp_file_path) if audio.channels != 1: audio = audio.set_channels(1) audio = audio.set_frame_rate(16000) # 将转换后的音频文件保存到临时文件中 audio.export(temp_file_path, format='mp3') # 识别临时文件 result = recognition.call(temp_file_path) text = '' if result.status_code == 200: result_sentence = result.get_sentence() if result_sentence is not None: for sentence in result_sentence: text += sentence['text'] return text else: raise Exception('Error: ', result.message) finally: # 删除临时文件 os.remove(temp_file_path) ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py ================================================ # coding=utf-8 from http import HTTPStatus from typing import Dict from dashscope import ImageSynthesis, MultiModalConversation from dashscope.aigc.image_generation import ImageGeneration from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage class QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage): api_key: str model_name: str params: dict api_base: str def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model_name = kwargs.get('model_name') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024*1024', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value api_base = model_credential.get('api_base') if api_base is None: api_base = 'https://dashscope.aliyuncs.com/api/v1' chat_tong_yi = QwenTextToImageModel( model_name=model_name, api_key=model_credential.get('api_key'), api_base=api_base, **optional_params, ) return chat_tong_yi def check_auth(self): # from openai import OpenAI # # client = OpenAI( # # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx" # api_key=self.api_key, # base_url=self.api_base, # ) # client.chat.completions.create( # # 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models # model="qwen-max", # messages=[ # {"role": "system", "content": "You are a helpful assistant."}, # {"role": "user", "content": gettext('Hello')}, # ] # # ) return True def generate_image(self, prompt: str, negative_prompt: str = None): import dashscope dashscope.base_http_api_url = self.api_base if self.model_name.startswith("wan2.6") or self.model_name.startswith("z"): from dashscope.api_entities.dashscope_response import Message # 以下为北京地域url,各地域的base_url不同 message = Message( role="user", content=[ { 'text': prompt } ] ) rsp = ImageGeneration.call( model=self.model_name, api_key=self.api_key, messages=[message], negative_prompt=negative_prompt, **self.params ) file_urls = [] if rsp.status_code == HTTPStatus.OK: for result in rsp.output.choices: file_urls.append(result.message.content[0].get('image')) else: maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) return file_urls elif self.model_name.startswith("wan") or self.model_name.startswith("qwen-image-plus"): rsp = ImageSynthesis.call(api_key=self.api_key, model=self.model_name, prompt=prompt, negative_prompt=negative_prompt, **self.params) file_urls = [] if rsp.status_code == HTTPStatus.OK: for result in rsp.output.results: file_urls.append(result.url) else: maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) return file_urls elif self.model_name.startswith("qwen"): messages = [ { "role": "user", "content": [ { "type": "text", "text": prompt } ] } ] rsp = MultiModalConversation.call( api_key=self.api_key, model=self.model_name, messages=messages, result_format='message', stream=False, negative_prompt=negative_prompt, **self.params ) file_urls = [] if rsp.status_code == HTTPStatus.OK: for result in rsp.output.choices: file_urls.append(result.message.content[0].get('image')) else: maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' % (rsp.status_code, rsp.code, rsp.message)) return file_urls ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py ================================================ from typing import Dict import dashscope from django.utils.translation import gettext as _ from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech class AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': 'longxiaochun', 'speech_rate': 1.0}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return AliyunBaiLianTextToSpeech( model=model_name, api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): self.text_to_speech(_('Hello')) def text_to_speech(self, text): dashscope.api_key = self.api_key text = _remove_empty_lines(text) if 'sambert' in self.model: from dashscope.audio.tts import SpeechSynthesizer audio = SpeechSynthesizer.call(model=self.model, text=text, **self.params).get_audio_data() else: from dashscope.audio.tts_v2 import SpeechSynthesizer synthesizer = SpeechSynthesizer(model=self.model, **self.params) audio = synthesizer.call(text) if audio is None: raise Exception('Failed to generate audio') if type(audio) == str: raise Exception(audio) return audio ================================================ FILE: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py ================================================ import time from http import HTTPStatus from typing import Dict import requests from dashscope import VideoSynthesis from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.base_ttv import BaseGenerationVideo class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): api_key: str model_name: str params: dict max_retries: int = 3 retry_delay: int = 5 # seconds def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model_name = kwargs.get('model_name') self.params = kwargs.get('params', {}) self.max_retries = kwargs.get('max_retries', 3) self.retry_delay = 5 @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return GenerationVideoModel( model_name=model_name, api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): return True def _safe_call(self, func, **kwargs): """带重试的请求封装""" for attempt in range(self.max_retries): try: rsp = func(**kwargs) return rsp except (requests.exceptions.ProxyError, requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: maxkb_logger.error(f"⚠️ 网络错误: {e},正在重试 {attempt + 1}/{self.max_retries}...") time.sleep(self.retry_delay) raise RuntimeError("多次重试后仍无法连接到 DashScope API,请检查代理或网络配置") # --- 通用异步生成函数 --- def generate_video(self, prompt, negative_prompt=None, first_frame_url=None, last_frame_url=None, **kwargs): """ prompt: 文本描述 negative_prompt: 反向文本描述 first_frame_url: 起始关键帧图片 URL (KF2V 必填) last_frame_url: 结束关键帧图片 URL (KF2V 必填) 如果没有提供last_frame_url,则表示只提供了first_frame_url,生成的是单关键帧视频(KFV) 参数是img_url """ # 构建基础参数 params = {"api_key": self.api_key, "prompt": prompt, "model": self.model_name, "negative_prompt": negative_prompt} if first_frame_url and last_frame_url: params['first_frame_url'] = first_frame_url params["last_frame_url"] = last_frame_url elif first_frame_url: params['img_url'] = first_frame_url # 合并所有额外参数 params.update(self.params) # --- 异步提交任务 --- rsp = self._safe_call(VideoSynthesis.async_call, **params) if rsp.status_code != HTTPStatus.OK: maxkb_logger.info(f'提交任务失败,status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}') raise RuntimeError(f'提交任务失败,status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}') maxkb_logger.info("task_id:", rsp.output.task_id) # --- 查询任务状态 --- status = self._safe_call(VideoSynthesis.fetch, task=rsp, api_key=self.api_key) if status.status_code == HTTPStatus.OK: maxkb_logger.info("当前任务状态:", status.output.task_status) else: maxkb_logger.error( f'获取任务状态失败,status_code: {status.status_code}, code: {status.code}, message: {status.message}') raise RuntimeError( f'获取任务状态失败,status_code: {status.status_code}, code: {status.code}, message: {status.message}') # --- 等待任务完成 --- rsp = self._safe_call(VideoSynthesis.wait, task=rsp, api_key=self.api_key) if rsp.status_code == HTTPStatus.OK: maxkb_logger.info("视频生成完成!视频 URL:", rsp.output.video_url) if rsp.output.task_status == "SUCCEEDED": maxkb_logger.info("视频生成完成!视频 URL:", rsp.output.video_url) return rsp.output.video_url else: maxkb_logger.error("视频生成失败!") raise RuntimeError(f'生成失败, message: {rsp.output.message}') else: maxkb_logger.error(f'生成失败,status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}') raise RuntimeError(f'生成失败,status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}') ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/anthropic_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: openai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.anthropic_model_provider.credential.image import AnthropicImageModelCredential from models_provider.impl.anthropic_model_provider.credential.llm import AnthropicLLMModelCredential from models_provider.impl.anthropic_model_provider.model.image import AnthropicImage from models_provider.impl.anthropic_model_provider.model.llm import AnthropicChatModel from maxkb.conf import PROJECT_DIR openai_llm_model_credential = AnthropicLLMModelCredential() openai_image_model_credential = AnthropicImageModelCredential() model_info_list = [ ModelInfo('claude-3-opus-20240229', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel ), ModelInfo('claude-3-sonnet-20240229', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel), ModelInfo('claude-3-haiku-20240307', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel), ModelInfo('claude-3-5-sonnet-20240620', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel), ModelInfo('claude-3-5-haiku-20241022', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel), ModelInfo('claude-3-5-sonnet-20241022', '', ModelTypeConst.LLM, openai_llm_model_credential, AnthropicChatModel), ] image_model_info = [ ModelInfo('claude-3-5-sonnet-20241022', '', ModelTypeConst.IMAGE, openai_image_model_credential, AnthropicImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info(model_info_list[0]) .append_model_info_list(image_model_info) .append_default_model_info(image_model_info[0]) .build() ) class AnthropicModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_anthropic_provider', name='Anthropic', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'anthropic_model_provider', 'icon', 'anthropic_icon_svg'))) ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class AnthropicImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class AnthropicImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField(_('API URL'), required=True) api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext("Hello")}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return AnthropicImageModelParams() ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext from common.utils.logger import maxkb_logger class AnthropicLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class AnthropicLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField(_('API URL'), required=True) api_key = forms.PasswordInputField(_('API Key'), required=True) def get_model_params_setting_form(self, model_name): return AnthropicLLMModelParams() ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/model/image.py ================================================ from typing import Dict from langchain_anthropic import ChatAnthropic from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AnthropicImage(MaxKBBaseModel, ChatAnthropic): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return AnthropicImage( model=model_name, anthropic_api_url=model_credential.get('api_base'), anthropic_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, **optional_params, ) ================================================ FILE: apps/models_provider/impl/anthropic_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import List, Dict from langchain_anthropic import ChatAnthropic from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AnthropicChatModel(MaxKBBaseModel, ChatAnthropic): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) azure_chat_open_ai = AnthropicChatModel( model=model_name, anthropic_api_url=model_credential.get('api_base'), anthropic_api_key=model_credential.get('api_key'), **optional_params, custom_get_token_ids=custom_get_token_ids ) return azure_chat_open_ai def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/__init__.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- import os from common.utils.common import get_file_content from models_provider.base_model_provider import ( IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, ModelInfoManage ) from models_provider.impl.aws_bedrock_model_provider.credential.embedding import BedrockEmbeddingCredential from models_provider.impl.aws_bedrock_model_provider.credential.image import BedrockVLModelCredential from models_provider.impl.aws_bedrock_model_provider.credential.llm import BedrockLLMModelCredential from models_provider.impl.aws_bedrock_model_provider.credential.reranker import BedrockRerankerCredential from models_provider.impl.aws_bedrock_model_provider.model.embedding import BedrockEmbeddingModel from models_provider.impl.aws_bedrock_model_provider.model.image import BedrockVLModel from models_provider.impl.aws_bedrock_model_provider.model.llm import BedrockModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ from models_provider.impl.aws_bedrock_model_provider.model.reranker import BedrockRerankerModel def _create_model_info(model_name, description, model_type, credential_class, model_class): return ModelInfo( name=model_name, desc=description, model_type=model_type, model_credential=credential_class(), model_class=model_class ) def _get_aws_bedrock_icon_path(): return os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'aws_bedrock_model_provider', 'icon', 'bedrock_icon_svg') def _initialize_model_info(): model_info_list = [ _create_model_info( 'anthropic.claude-v2:1', _('An update to Claude 2 that doubles the context window and improves reliability, hallucination rates, and evidence-based accuracy in long documents and RAG contexts.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'anthropic.claude-v2', _('Anthropic is a powerful model that can handle a variety of tasks, from complex dialogue and creative content generation to detailed command obedience.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'anthropic.claude-3-haiku-20240307-v1:0', _("The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-instant responsiveness. The model can answer simple queries and requests quickly. Customers will be able to build seamless AI experiences that mimic human interactions. Claude 3 Haiku can process images and return text output, and provides 200K context windows."), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'anthropic.claude-3-sonnet-20240229-v1:0', _("The Claude 3 Sonnet model from Anthropic strikes the ideal balance between intelligence and speed, especially when it comes to handling enterprise workloads. This model offers maximum utility while being priced lower than competing products, and it's been engineered to be a solid choice for deploying AI at scale."), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'anthropic.claude-3-5-sonnet-20240620-v1:0', _('The Claude 3.5 Sonnet raises the industry standard for intelligence, outperforming competing models and the Claude 3 Opus in extensive evaluations, with the speed and cost-effectiveness of our mid-range models.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'anthropic.claude-instant-v1', _('A faster, more affordable but still very powerful model that can handle a range of tasks including casual conversation, text analysis, summarization and document question answering.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'amazon.titan-text-premier-v1:0', _("Titan Text Premier is the most powerful and advanced model in the Titan Text series, designed to deliver exceptional performance for a variety of enterprise applications. With its cutting-edge features, it delivers greater accuracy and outstanding results, making it an excellent choice for organizations looking for a top-notch text processing solution."), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel ), _create_model_info( 'amazon.titan-text-lite-v1', _('Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-tuning English-language tasks, including summarization and copywriting, where customers require smaller, more cost-effective, and highly customizable models.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), _create_model_info( 'amazon.titan-text-express-v1', _('Amazon Titan Text Express has context lengths of up to 8,000 tokens, making it ideal for a variety of high-level general language tasks, such as open-ended text generation and conversational chat, as well as support in retrieval-augmented generation (RAG). At launch, the model is optimized for English, but other languages are supported.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), _create_model_info( 'mistral.mistral-7b-instruct-v0:2', _('7B dense converter for rapid deployment and easy customization. Small in size yet powerful in a variety of use cases. Supports English and code, as well as 32k context windows.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), _create_model_info( 'mistral.mistral-large-2402-v1:0', _('Advanced Mistral AI large-scale language model capable of handling any language task, including complex multilingual reasoning, text understanding, transformation, and code generation.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), _create_model_info( 'meta.llama3-70b-instruct-v1:0', _('Ideal for content creation, conversational AI, language understanding, R&D, and enterprise applications'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), _create_model_info( 'meta.llama3-8b-instruct-v1:0', _('Ideal for limited computing power and resources, edge devices, and faster training times.'), ModelTypeConst.LLM, BedrockLLMModelCredential, BedrockModel), ] embedded_model_info_list = [ _create_model_info( 'amazon.titan-embed-text-v1', _('Titan Embed Text is the largest embedding model in the Amazon Titan Embed series and can handle various text embedding tasks, such as text classification, text similarity calculation, etc.'), ModelTypeConst.EMBEDDING, BedrockEmbeddingCredential, BedrockEmbeddingModel ), ] reranker_model_info_list = [ _create_model_info( 'amazon.rerank-v1:0', '', ModelTypeConst.RERANKER, BedrockRerankerCredential, BedrockRerankerModel ), _create_model_info( 'cohere.rerank-v3-5:0', '', ModelTypeConst.RERANKER, BedrockRerankerCredential, BedrockRerankerModel ) ] vl_model_info_list = [ _create_model_info( 'global.anthropic.claude-sonnet-4-5-20250929-v1:0', '', ModelTypeConst.IMAGE, BedrockVLModelCredential, BedrockVLModel ), _create_model_info( 'us.anthropic.claude-sonnet-4-5-20250929-v1:0', '', ModelTypeConst.IMAGE, BedrockVLModelCredential, BedrockVLModel ), _create_model_info( 'global.anthropic.claude-haiku-4-5-20251001-v1:0', '', ModelTypeConst.IMAGE, BedrockVLModelCredential, BedrockVLModel ), ] model_info_manage = ModelInfoManage.builder() \ .append_model_info_list(model_info_list) \ .append_default_model_info(model_info_list[0]) \ .append_model_info_list(embedded_model_info_list) \ .append_default_model_info(embedded_model_info_list[0]) \ .append_model_info_list(vl_model_info_list) \ .append_default_model_info(vl_model_info_list[0]) \ .append_model_info_list(reranker_model_info_list) \ .append_default_model_info(reranker_model_info_list[0]) \ .build() return model_info_manage class BedrockModelProvider(IModelProvider): def __init__(self): self._model_info_manage = _initialize_model_info() def get_model_info_manage(self): return self._model_info_manage def get_model_provide_info(self): icon_path = _get_aws_bedrock_icon_path() icon_data = get_file_content(icon_path) return ModelProvideInfo( provider='model_aws_bedrock_provider', name='Amazon Bedrock', icon=icon_data ) ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py ================================================ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.aws_bedrock_model_provider.model.embedding import BedrockEmbeddingModel from common.utils.logger import maxkb_logger class BedrockEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) return False required_keys = ['region_name', 'access_key_id', 'secret_access_key'] if not all(key in model_credential for key in required_keys): if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('The following fields are required: {keys}').format( keys=", ".join(required_keys))) return False try: model: BedrockEmbeddingModel = provider.get_model(model_type, model_name, model_credential) aa = model.embed_query(_('Hello')) except AppApiException: raise except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))} region_name = forms.TextInputField('Region Name', required=True) access_key_id = forms.TextInputField('Access Key ID', required=True) secret_access_key = forms.PasswordInputField('Secret Access Key', required=True) ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/credential/image.py ================================================ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import ValidCode, BaseModelCredential from common.utils.logger import maxkb_logger class BedrockImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class BedrockVLModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) return False required_keys = ['region_name', 'access_key_id', 'secret_access_key'] if not all(key in model_credential for key in required_keys): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('The following fields are required: {keys}').format( keys=", ".join(required_keys))) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except AppApiException: raise except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))} region_name = forms.TextInputField('Region Name', required=True) access_key_id = forms.TextInputField('Access Key ID', required=True) secret_access_key = forms.PasswordInputField('Secret Access Key', required=True) base_url = forms.TextInputField('Proxy URL', required=False) def get_model_params_setting_form(self, model_name): return BedrockImageModelParams() ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py ================================================ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import ValidCode, BaseModelCredential from common.utils.logger import maxkb_logger class BedrockLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class BedrockLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(mt.get('value') == model_type for mt in model_type_list): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) return False required_keys = ['region_name', 'access_key_id', 'secret_access_key'] if not all(key in model_credential for key in required_keys): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('The following fields are required: {keys}').format( keys=", ".join(required_keys))) return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except AppApiException: raise except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))} region_name = forms.TextInputField('Region Name', required=True) access_key_id = forms.TextInputField('Access Key ID', required=True) secret_access_key = forms.PasswordInputField('Secret Access Key', required=True) base_url = forms.TextInputField('Proxy URL', required=False) def get_model_params_setting_form(self, model_name): return BedrockLLMModelParams() ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/credential/reranker.py ================================================ import traceback from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import ValidCode, BaseModelCredential class BedrockRerankerModelParams(BaseForm): top_n = forms.SliderField(TooltipLabel(_('Top N'), _('Number of top documents to return after reranking')), required=True, default_value=3, _min=1, _max=20, _step=1, precision=0) class BedrockRerankerCredential(BaseForm, BaseModelCredential): access_key_id = forms.PasswordInputField(_('Access Key ID'), required=True) secret_access_key = forms.PasswordInputField(_('Secret Access Key'), required=True) region_name = forms.TextInputField(_('Region Name'), required=True, default_value='us-east-1') base_url = forms.TextInputField(_('Base URL'), required=False) def is_valid(self, model_type: str, model_name: str, model_credential: Dict[str, object], model_params, provider, raise_exception: bool = False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('Model type is not supported')) for key in ['access_key_id', 'secret_access_key', 'region_name']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('%(key)s is required') % {'key': key}) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) # Use top_n=1 for validation since we only have 1 test document test_docs = [ Document(page_content=str(_('Hello'))), Document(page_content=str(_('World'))), Document(page_content=str(_('Test'))) ] model.compress_documents(test_docs, str(_('Hello'))) except Exception as e: traceback.print_exc() if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: %(error)s') % {'error': str(e)}) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'access_key_id': super().encryption(model.get('access_key_id', '')), 'secret_access_key': super().encryption(model.get('secret_access_key', ''))} def get_model_params_setting_form(self, model_name): return BedrockRerankerModelParams() ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/model/embedding.py ================================================ from langchain_aws import BedrockEmbeddings from models_provider.base_model_provider import MaxKBBaseModel from typing import Dict, List from models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials class BedrockEmbeddingModel(MaxKBBaseModel, BedrockEmbeddings): def __init__(self, model_id: str, region_name: str, credentials_profile_name: str, **kwargs): super().__init__(model_id=model_id, region_name=region_name, credentials_profile_name=credentials_profile_name, **kwargs) @classmethod def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs) -> 'BedrockModel': _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'], model_credential['secret_access_key']) return cls( model_id=model_name, region_name=model_credential['region_name'], credentials_profile_name=model_credential['access_key_id'], ) def embed_documents(self, texts: List[str]) -> List[List[float]]: """Compute doc embeddings using a Bedrock model. Args: texts: The list of texts to embed Returns: List of embeddings, one for each text. """ results = [] for text in texts: response = self._embedding_func(text) if self.normalize: response = self._normalize_vector(response) results.append(response) return results def embed_query(self, text: str) -> List[float]: """Compute query embeddings using a Bedrock model. Args: text: The text to embed. Returns: Embeddings for the text. """ embedding = self._embedding_func(text) if self.normalize: return self._normalize_vector(embedding) return embedding ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/model/image.py ================================================ # coding=utf-8 """ @project: MaxKB @file: image.py @desc: AWS Bedrock Vision-Language Model Implementation """ from typing import Dict, List from botocore.config import Config from langchain_aws import ChatBedrock from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials class BedrockVLModel(MaxKBBaseModel, ChatBedrock): """ AWS Bedrock Vision-Language Model Supports Claude 3 models with vision capabilities (Haiku, Sonnet, Opus) """ @staticmethod def is_cache_model(): return False def __init__(self, model_id: str, region_name: str, credentials_profile_name: str, streaming: bool = False, config: Config = None, **kwargs): super().__init__( model_id=model_id, region_name=region_name, credentials_profile_name=credentials_profile_name, streaming=streaming, config=config, **kwargs ) @classmethod def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs) -> 'BedrockVLModel': optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) config = {} # Check if proxy URL is provided if 'base_url' in model_credential and model_credential['base_url']: proxy_url = model_credential['base_url'] config = Config( proxies={ 'http': proxy_url, 'https': proxy_url }, connect_timeout=60, read_timeout=60 ) _update_aws_credentials( model_credential['access_key_id'], model_credential['access_key_id'], model_credential['secret_access_key'] ) return cls( model_id=model_name, region_name=model_credential['region_name'], credentials_profile_name=model_credential['access_key_id'], streaming=model_kwargs.pop('streaming', True), model_kwargs=optional_params, config=config ) def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: """ Get the number of tokens from messages Falls back to local tokenizer if the model's tokenizer fails """ try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: """ Get the number of tokens from text Falls back to local tokenizer if the model's tokenizer fails """ try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py ================================================ import os import re from typing import Dict, List from botocore.config import Config from langchain_aws import ChatBedrock from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def get_max_tokens_keyword(model_name): """ 根据模型名称返回正确的 max_tokens 关键字。 :param model_name: 模型名称字符串 :return: 对应的 max_tokens 关键字字符串 """ maxTokens = ["ai21.j2-ultra-v1", "ai21.j2-mid-v1"] # max_tokens_to_sample = ["anthropic.claude-v2:1", "anthropic.claude-v2", "anthropic.claude-instant-v1"] maxTokenCount = ["amazon.titan-text-lite-v1", "amazon.titan-text-express-v1"] max_new_tokens = [ "us.meta.llama3-2-1b-instruct-v1:0", "us.meta.llama3-2-3b-instruct-v1:0", "us.meta.llama3-2-11b-instruct-v1:0", "us.meta.llama3-2-90b-instruct-v1:0"] if model_name in maxTokens: return 'maxTokens' elif model_name in maxTokenCount: return 'maxTokenCount' elif model_name in max_new_tokens: return 'max_new_tokens' else: return 'max_tokens' class BedrockModel(MaxKBBaseModel, ChatBedrock): @staticmethod def is_cache_model(): return False def __init__(self, model_id: str, region_name: str, credentials_profile_name: str, streaming: bool = False, config: Config = None, **kwargs): super().__init__(model_id=model_id, region_name=region_name, credentials_profile_name=credentials_profile_name, streaming=streaming, config=config, **kwargs) @classmethod def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs) -> 'BedrockModel': optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) config = {} # 判断model_kwargs是否包含 base_url 且不为空 if 'base_url' in model_credential and model_credential['base_url']: proxy_url = model_credential['base_url'] config = Config( proxies={ 'http': proxy_url, 'https': proxy_url }, connect_timeout=60, read_timeout=60 ) _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'], model_credential['secret_access_key']) return cls( model_id=model_name, region_name=model_credential['region_name'], credentials_profile_name=model_credential['access_key_id'], streaming=model_kwargs.pop('streaming', True), model_kwargs=optional_params, config=config ) def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) def _update_aws_credentials(profile_name, access_key_id, secret_access_key): credentials_path = os.path.join(os.path.expanduser("~"), ".aws", "credentials") os.makedirs(os.path.dirname(credentials_path), exist_ok=True) content = open(credentials_path, 'r').read() if os.path.exists(credentials_path) else '' pattern = rf'\n*\[{profile_name}\]\n*(aws_access_key_id = .*)\n*(aws_secret_access_key = .*)\n*' content = re.sub(pattern, '', content, flags=re.DOTALL) if not re.search(rf'\[{profile_name}\]', content): content += f"\n[{profile_name}]\naws_access_key_id = {access_key_id}\naws_secret_access_key = {secret_access_key}\n" with open(credentials_path, 'w') as file: file.write(content) ================================================ FILE: apps/models_provider/impl/aws_bedrock_model_provider/model/reranker.py ================================================ import os import re from typing import Dict, List, Sequence, Optional, Any from botocore.config import Config from langchain_aws import BedrockRerank from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from pydantic import ConfigDict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials class BedrockRerankerModel(MaxKBBaseModel, BaseDocumentCompressor): model_config = ConfigDict(arbitrary_types_allowed=True) model_id: Optional[str] = None model_arn: Optional[str] = None region_name: Optional[str] = None credentials_profile_name: Optional[str] = None aws_access_key_id: Optional[str] = None aws_secret_access_key: Optional[str] = None config: Optional[Any] = None top_n: Optional[int] = 3 @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs) -> 'BedrockRerankerModel': top_n = model_kwargs.get('top_n', 3) region_name = model_credential['region_name'] model_arn = f"arn:aws:bedrock:{region_name}::foundation-model/{model_name}" config = None if 'base_url' in model_credential and model_credential['base_url']: proxy_url = model_credential['base_url'] config = Config( proxies={ 'http': proxy_url, 'https': proxy_url }, connect_timeout=60, read_timeout=60 ) _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'], model_credential['secret_access_key']) return BedrockRerankerModel( model_id=model_name, model_arn=model_arn, region_name=region_name, credentials_profile_name=model_credential['access_key_id'], aws_access_key_id=model_credential['access_key_id'], aws_secret_access_key=model_credential['secret_access_key'], config=config, top_n=top_n ) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> Sequence[Document]: """Compress documents using Bedrock reranking.""" if not documents: return [] reranker = BedrockRerank( model_arn=self.model_arn, region_name=self.region_name, credentials_profile_name=self.credentials_profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, config=self.config, top_n=self.top_n ) return reranker.compress_documents(documents, query, callbacks) ================================================ FILE: apps/models_provider/impl/azure_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2023/10/31 17:16 @desc: """ ================================================ FILE: apps/models_provider/impl/azure_model_provider/azure_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: azure_model_provider.py @date:2023/10/31 16:19 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.azure_model_provider.credential.embedding import AzureOpenAIEmbeddingCredential from models_provider.impl.azure_model_provider.credential.image import AzureOpenAIImageModelCredential from models_provider.impl.azure_model_provider.credential.llm import AzureLLMModelCredential from models_provider.impl.azure_model_provider.credential.stt import AzureOpenAISTTModelCredential from models_provider.impl.azure_model_provider.credential.tti import AzureOpenAITextToImageModelCredential from models_provider.impl.azure_model_provider.credential.tts import AzureOpenAITTSModelCredential from models_provider.impl.azure_model_provider.model.azure_chat_model import AzureChatModel from models_provider.impl.azure_model_provider.model.embedding import AzureOpenAIEmbeddingModel from models_provider.impl.azure_model_provider.model.image import AzureOpenAIImage from models_provider.impl.azure_model_provider.model.stt import AzureOpenAISpeechToText from models_provider.impl.azure_model_provider.model.tti import AzureOpenAITextToImage from models_provider.impl.azure_model_provider.model.tts import AzureOpenAITextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext_lazy as _ base_azure_llm_model_credential = AzureLLMModelCredential() base_azure_embedding_model_credential = AzureOpenAIEmbeddingCredential() base_azure_image_model_credential = AzureOpenAIImageModelCredential() base_azure_tti_model_credential = AzureOpenAITextToImageModelCredential() base_azure_tts_model_credential = AzureOpenAITTSModelCredential() base_azure_stt_model_credential = AzureOpenAISTTModelCredential() default_model_info = [ ModelInfo('Azure OpenAI', '', ModelTypeConst.LLM, base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview' ), ModelInfo('gpt-4', '', ModelTypeConst.LLM, base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview' ), ModelInfo('gpt-4o', '', ModelTypeConst.LLM, base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview' ), ModelInfo('gpt-4o-mini', '', ModelTypeConst.LLM, base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview' ), ] embedding_model_info = [ ModelInfo('text-embedding-3-large', '', ModelTypeConst.EMBEDDING, base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15' ), ModelInfo('text-embedding-3-small', '', ModelTypeConst.EMBEDDING, base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15' ), ModelInfo('text-embedding-ada-002', '', ModelTypeConst.EMBEDDING, base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15' ), ] image_model_info = [ ModelInfo('gpt-4o', '', ModelTypeConst.IMAGE, base_azure_image_model_credential, AzureOpenAIImage, api_version='2023-05-15' ), ModelInfo('gpt-4o-mini', '', ModelTypeConst.IMAGE, base_azure_image_model_credential, AzureOpenAIImage, api_version='2023-05-15' ), ] tti_model_info = [ ModelInfo('dall-e-3', '', ModelTypeConst.TTI, base_azure_tti_model_credential, AzureOpenAITextToImage, api_version='2023-05-15' ), ] tts_model_info = [ ModelInfo('tts', '', ModelTypeConst.TTS, base_azure_tts_model_credential, AzureOpenAITextToSpeech, api_version='2023-05-15' ), ] stt_model_info = [ ModelInfo('whisper', '', ModelTypeConst.STT, base_azure_stt_model_credential, AzureOpenAISpeechToText, api_version='2023-05-15' ), ] model_info_manage = ( ModelInfoManage.builder() .append_default_model_info(default_model_info[0]) .append_model_info_list(default_model_info) .append_model_info_list(embedding_model_info) .append_default_model_info(embedding_model_info[0]) .append_model_info_list(image_model_info) .append_default_model_info(image_model_info[0]) .append_model_info_list(stt_model_info) .append_default_model_info(stt_model_info[0]) .append_model_info_list(tts_model_info) .append_default_model_info(tts_model_info[0]) .append_model_info_list(tti_model_info) .append_default_model_info(tti_model_info[0]) .build() ) class AzureModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_azure_provider', name='Azure OpenAI', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'azure_model_provider', 'icon', 'azure_icon_svg'))) ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 17:08 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AzureOpenAIEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct')) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_version = forms.TextInputField("Api Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext class AzureOpenAIImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class AzureOpenAIImageModelCredential(BaseForm, BaseModelCredential): api_version = forms.TextInputField("API Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return AzureOpenAIImageModelParams() ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 17:08 @desc: """ from typing import Dict from langchain_core.messages import HumanMessage from openai import BadRequestError from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext from common.utils.logger import maxkb_logger class AzureLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class o3MiniLLMModelParams(BaseForm): max_completion_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=5000, _step=1, precision=0) class AzureLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'deployment_name', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException) or isinstance(e, BadRequestError): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('Verification failed, please check whether the parameters are correct')) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_version = forms.TextInputField("API Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) deployment_name = forms.TextInputField("Deployment name", required=True) def get_model_params_setting_form(self, model_name): if 'o3' in model_name or 'o1' in model_name: return o3MiniLLMModelParams() return AzureLLMModelParams() ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AzureOpenAISTTModelCredential(BaseForm, BaseModelCredential): api_version = forms.TextInputField("API Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AzureOpenAITTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('Specify the size of the generated image, such as: 1024x1024')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), ''), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('Specify the number of generated images')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class AzureOpenAITextToImageModelCredential(BaseForm, BaseModelCredential): api_version = forms.TextInputField("API Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return AzureOpenAITTIModelParams() ================================================ FILE: apps/models_provider/impl/azure_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class AzureOpenAITTSModelGeneralParams(BaseForm): # alloy, echo, fable, onyx, nova, shimmer voice = forms.SingleSelect( TooltipLabel('Voice', _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')), required=True, default_value='alloy', text_field='value', value_field='value', option_list=[ {'text': 'alloy', 'value': 'alloy'}, {'text': 'echo', 'value': 'echo'}, {'text': 'fable', 'value': 'fable'}, {'text': 'onyx', 'value': 'onyx'}, {'text': 'nova', 'value': 'nova'}, {'text': 'shimmer', 'value': 'shimmer'}, ]) class AzureOpenAITTSModelCredential(BaseForm, BaseModelCredential): api_version = forms.TextInputField("API Version", required=True) api_base = forms.TextInputField('Azure Endpoint', required=True) api_key = forms.PasswordInputField("API Key", required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key', 'api_version']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return AzureOpenAITTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/azure_chat_model.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: azure_chat_model.py @date:2024/4/28 11:45 @desc: """ from typing import List, Dict, Optional, Any from langchain_core.language_models import LanguageModelInput from langchain_core.messages import BaseMessage, get_buffer_string from langchain_core.runnables import RunnableConfig from langchain_openai import AzureChatOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel class AzureChatModel(MaxKBBaseModel, AzureChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return AzureChatModel( azure_endpoint=model_credential.get('api_base'), model_name=model_name, openai_api_version=model_credential.get('api_version', '2024-02-15-preview'), deployment_name=model_credential.get('deployment_name'), openai_api_key=model_credential.get('api_key'), openai_api_type="azure", **optional_params, streaming=True, ) def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.__dict__.get('_last_generation_info') def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return self.get_last_generation_info().get('input_tokens', 0) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return self.get_last_generation_info().get('output_tokens', 0) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) def invoke( self, input: LanguageModelInput, config: Optional[RunnableConfig] = None, *, stop: Optional[list[str]] = None, **kwargs: Any, ) -> BaseMessage: message = super().invoke(input, config, stop=stop, **kwargs) if isinstance(message.content, str): return message elif isinstance(message.content, list): # 构造新的响应消息返回 content = message.content normalized_parts = [] for item in content: if isinstance(item, dict): if item.get('type') == 'text': normalized_parts.append(item.get('text', '')) message.content = ''.join(normalized_parts) self.__dict__.setdefault('_last_generation_info', {}).update(message.usage_metadata) return message ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict from langchain_openai import AzureOpenAIEmbeddings from models_provider.base_model_provider import MaxKBBaseModel class AzureOpenAIEmbeddingModel(MaxKBBaseModel, AzureOpenAIEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return AzureOpenAIEmbeddingModel( model=model_name, openai_api_key=model_credential.get('api_key'), azure_endpoint=model_credential.get('api_base'), openai_api_version=model_credential.get('api_version'), openai_api_type="azure", ) ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/image.py ================================================ from typing import Dict, List from langchain_core.messages import BaseMessage, get_buffer_string from langchain_openai import AzureChatOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AzureOpenAIImage(MaxKBBaseModel, AzureChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return AzureOpenAIImage( model_name=model_name, openai_api_key=model_credential.get('api_key'), azure_endpoint=model_credential.get('api_base'), openai_api_version=model_credential.get('api_version'), openai_api_type="azure", streaming=True, **optional_params, ) def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/stt.py ================================================ import io from typing import Dict from openai import AzureOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AzureOpenAISpeechToText(MaxKBBaseModel, BaseSpeechToText): api_base: str api_key: str api_version: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.api_version = kwargs.get('api_version') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return AzureOpenAISpeechToText( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), api_version=model_credential.get('api_version'), params=model_kwargs, **optional_params, ) def check_auth(self): client = AzureOpenAI( azure_endpoint=self.api_base, api_key=self.api_key, api_version=self.api_version ) response_list = client.models.with_raw_response.list() # print(response_list) def speech_to_text(self, audio_file): client = AzureOpenAI( azure_endpoint=self.api_base, api_key=self.api_key, api_version=self.api_version ) audio_data = audio_file.read() buffer = io.BytesIO(audio_data) buffer.name = "file.mp3" # this is the important line filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}} transcription_params = { 'model': self.model, 'file': buffer, 'language': 'zh' } res = client.audio.transcriptions.create(**transcription_params, extra_body=filter_params) return res.text ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/tti.py ================================================ from typing import Dict from openai import AzureOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AzureOpenAITextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str api_version: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.api_version = kwargs.get('api_version') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return AzureOpenAITextToImage( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), api_version=model_credential.get('api_version'), **optional_params, ) def check_auth(self): chat = AzureOpenAI(api_key=self.api_key, azure_endpoint=self.api_base, api_version=self.api_version) response_list = chat.models.with_raw_response.list() # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = AzureOpenAI(api_key=self.api_key, azure_endpoint=self.api_base, api_version=self.api_version) res = chat.images.generate(model=self.model, prompt=prompt, **self.params) file_urls = [] try: for content in res.data: url = content.url file_urls.append(url) return file_urls except Exception as e: raise e ================================================ FILE: apps/models_provider/impl/azure_model_provider/model/tts.py ================================================ from typing import Dict from openai import AzureOpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class AzureOpenAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_base: str api_key: str api_version: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.api_version = kwargs.get('api_version') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': 'alloy'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return AzureOpenAITextToSpeech( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), api_version=model_credential.get('api_version'), **optional_params, ) def check_auth(self): client = AzureOpenAI( azure_endpoint=self.api_base, api_key=self.api_key, api_version=self.api_version ) response_list = client.models.with_raw_response.list() # print(response_list) def text_to_speech(self, text): client = AzureOpenAI( azure_endpoint=self.api_base, api_key=self.api_key, api_version=self.api_version ) text = _remove_empty_lines(text) with client.audio.speech.with_streaming_response.create( model=self.model, input=text, **self.params ) as response: return response.read() ================================================ FILE: apps/models_provider/impl/base_chat_open_ai.py ================================================ # coding=utf-8 import base64 from concurrent.futures import ThreadPoolExecutor from typing import Dict, Optional, Any, Iterator, cast, Union, Sequence, Callable, Mapping from langchain_core.language_models import LanguageModelInput from langchain_core.messages import BaseMessage, get_buffer_string, BaseMessageChunk, HumanMessageChunk, AIMessageChunk, \ SystemMessageChunk, FunctionMessageChunk, ChatMessageChunk from langchain_core.messages.ai import UsageMetadata from langchain_core.messages.tool import tool_call_chunk, ToolMessageChunk from langchain_core.outputs import ChatGenerationChunk from langchain_core.runnables import RunnableConfig, ensure_config from langchain_core.tools import BaseTool from langchain_openai import ChatOpenAI from langchain_openai.chat_models.base import _create_usage_metadata from requests.exceptions import ReadTimeout from common.config.tokenizer_manage_config import TokenizerManage from common.utils.logger import maxkb_logger def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) def _convert_delta_to_message_chunk( _dict: Mapping[str, Any], default_class: type[BaseMessageChunk] ) -> BaseMessageChunk: """Convert to a LangChain message chunk.""" id_ = _dict.get("id") role = cast(str, _dict.get("role")) content = cast(str, _dict.get("content") or "") additional_kwargs: dict = {} if 'reasoning_content' in _dict: additional_kwargs['reasoning_content'] = _dict.get('reasoning_content') if _dict.get("function_call"): function_call = dict(_dict["function_call"]) if "name" in function_call and function_call["name"] is None: function_call["name"] = "" additional_kwargs["function_call"] = function_call tool_call_chunks = [] if raw_tool_calls := _dict.get("tool_calls"): try: tool_call_chunks = [ tool_call_chunk( name=rtc["function"].get("name"), args=rtc["function"].get("arguments"), id=rtc.get("id"), index=rtc["index"], ) for rtc in raw_tool_calls ] except KeyError: pass if role == "user" or default_class == HumanMessageChunk: return HumanMessageChunk(content=content, id=id_) if role == "assistant" or default_class == AIMessageChunk: return AIMessageChunk( content=content, additional_kwargs=additional_kwargs, id=id_, tool_call_chunks=tool_call_chunks, # type: ignore[arg-type] ) if role in ("system", "developer") or default_class == SystemMessageChunk: if role == "developer": additional_kwargs = {"__openai_role__": "developer"} else: additional_kwargs = {} return SystemMessageChunk( content=content, id=id_, additional_kwargs=additional_kwargs ) if role == "function" or default_class == FunctionMessageChunk: return FunctionMessageChunk(content=content, name=_dict["name"], id=id_) if role == "tool" or default_class == ToolMessageChunk: return ToolMessageChunk( content=content, tool_call_id=_dict["tool_call_id"], id=id_ ) if role or default_class == ChatMessageChunk: return ChatMessageChunk(content=content, role=role, id=id_) return default_class(content=content, id=id_) # type: ignore[call-arg]# class BaseChatOpenAI(ChatOpenAI): usage_metadata: dict = {} custom_get_token_ids = custom_get_token_ids def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.usage_metadata def get_num_tokens_from_messages( self, messages: list[BaseMessage], tools: Optional[ Sequence[Union[dict[str, Any], type, Callable, BaseTool]] ] = None, timeout: Optional[float] = 0.5, ) -> int: if self.usage_metadata is None or self.usage_metadata == {}: with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(super().get_num_tokens_from_messages, messages, tools) try: response = future.result() maxkb_logger.info("请求成功(未超时)") return response except Exception as e: if isinstance(e, ReadTimeout): raise # 继续抛出 else: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) 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 == {}: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) 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 for chunk in super()._stream(*args, **kwargs): if chunk.message.usage_metadata is not None: self.usage_metadata = chunk.message.usage_metadata yield chunk def _convert_chunk_to_generation_chunk( self, chunk: dict, default_chunk_class: type, base_generation_info: dict | None, ) -> ChatGenerationChunk | None: if chunk.get("type") == "content.delta": # From beta.chat.completions.stream return None token_usage = chunk.get("usage") choices = ( chunk.get("choices", []) # From beta.chat.completions.stream or chunk.get("chunk", {}).get("choices", []) ) usage_metadata: UsageMetadata | None = ( _create_usage_metadata(token_usage, chunk.get("service_tier")) if token_usage else None ) if len(choices) == 0: # logprobs is implicitly None generation_chunk = ChatGenerationChunk( message=default_chunk_class(content="", usage_metadata=usage_metadata), generation_info=base_generation_info, ) if self.output_version == "v1": generation_chunk.message.content = [] generation_chunk.message.response_metadata["output_version"] = "v1" return generation_chunk choice = choices[0] if choice["delta"] is None: return None message_chunk = _convert_delta_to_message_chunk( choice["delta"], default_chunk_class ) generation_info = {**base_generation_info} if base_generation_info else {} if finish_reason := choice.get("finish_reason"): generation_info["finish_reason"] = finish_reason if model_name := chunk.get("model"): generation_info["model_name"] = model_name if system_fingerprint := chunk.get("system_fingerprint"): generation_info["system_fingerprint"] = system_fingerprint if service_tier := chunk.get("service_tier"): generation_info["service_tier"] = service_tier logprobs = choice.get("logprobs") if logprobs: generation_info["logprobs"] = logprobs if usage_metadata and isinstance(message_chunk, AIMessageChunk): message_chunk.usage_metadata = usage_metadata message_chunk.response_metadata["model_provider"] = "openai" return ChatGenerationChunk( message=message_chunk, generation_info=generation_info or None ) def invoke( self, input: LanguageModelInput, config: Optional[RunnableConfig] = None, *, stop: Optional[list[str]] = None, **kwargs: Any, ) -> BaseMessage: config = ensure_config(config) chat_result = cast( "ChatGeneration", self.generate_prompt( [self._convert_input(input)], stop=stop, callbacks=config.get("callbacks"), tags=config.get("tags"), metadata=config.get("metadata"), run_name=config.get("run_name"), run_id=config.pop("run_id", None), **kwargs, ).generations[0][0], ).message self.usage_metadata = chat_result.response_metadata[ 'token_usage'] if 'token_usage' in chat_result.response_metadata else chat_result.usage_metadata return chat_result def upload_file_and_get_url(self, file_stream, file_name): """上传文件并获取文件URL""" base64_video = base64.b64encode(file_stream).decode("utf-8") video_format = get_video_format(file_name) return f'data:{video_format};base64,{base64_video}' def get_video_format(file_name): extension = file_name.split('.')[-1].lower() format_map = { 'mp4': 'video/mp4', 'avi': 'video/avi', 'mov': 'video/mov', 'wmv': 'video/x-ms-wmv' } return format_map.get(extension, 'video/mp4') ================================================ FILE: apps/models_provider/impl/base_stt.py ================================================ # coding=utf-8 from abc import abstractmethod from pydantic import BaseModel class BaseSpeechToText(BaseModel): @abstractmethod def check_auth(self): pass @abstractmethod def speech_to_text(self, audio_file): pass ================================================ FILE: apps/models_provider/impl/base_tti.py ================================================ # coding=utf-8 from abc import abstractmethod from pydantic import BaseModel class BaseTextToImage(BaseModel): @abstractmethod def check_auth(self): pass @abstractmethod def generate_image(self, prompt: str, negative_prompt: str = None): pass ================================================ FILE: apps/models_provider/impl/base_tts.py ================================================ # coding=utf-8 from abc import abstractmethod from pydantic import BaseModel class BaseTextToSpeech(BaseModel): @abstractmethod def check_auth(self): pass @abstractmethod def text_to_speech(self, text): pass ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/__init__.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :__init__.py.py @Author :Brian Yang @Date :5/12/24 7:38 AM """ ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 17:51 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DeepSeekLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class DeepSeekLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://api.deepseek.com') api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return DeepSeekLLMModelParams() ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :deepseek_model_provider.py @Author :Brian Yang @Date :5/12/24 7:40 AM """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ ModelInfoManage from models_provider.impl.deepseek_model_provider.credential.llm import DeepSeekLLMModelCredential from models_provider.impl.deepseek_model_provider.model.llm import DeepSeekChatModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ deepseek_llm_model_credential = DeepSeekLLMModelCredential() deepseek_reasoner = ModelInfo('deepseek-reasoner', '', ModelTypeConst.LLM, deepseek_llm_model_credential, DeepSeekChatModel ) deepseek_chat = ModelInfo('deepseek-chat', _('Good at common conversational tasks, supports 32K contexts'), ModelTypeConst.LLM, deepseek_llm_model_credential, DeepSeekChatModel ) deepseek_coder = ModelInfo('deepseek-coder', _('Good at handling programming tasks, supports 16K contexts'), ModelTypeConst.LLM, deepseek_llm_model_credential, DeepSeekChatModel) model_info_manage = ModelInfoManage.builder().append_model_info(deepseek_reasoner).append_model_info(deepseek_chat).append_model_info( deepseek_coder).append_default_model_info( deepseek_coder).build() class DeepSeekModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_deepseek_provider', name='DeepSeek', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'deepseek_model_provider', 'icon', 'deepseek_icon_svg'))) ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/deepseek_model_provider/model/llm.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :llm.py @Author :Brian Yang @Date :5/12/24 7:44 AM """ import json from typing import Dict, Any from langchain_core.language_models import LanguageModelInput from langchain_core.messages import AIMessage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class DeepSeekChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) deepseek_chat_open_ai = DeepSeekChatModel( model=model_name, openai_api_base=model_credential.get('api_base') or 'https://api.deepseek.com', openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) return deepseek_chat_open_ai def _get_request_payload( self, input_: LanguageModelInput, *, stop: list[str] | None = None, **kwargs: Any, ) -> dict: # Get original messages to preserve reasoning_content before base conversion messages = self._convert_input(input_).to_messages() # Store reasoning_content for AIMessages with tool_calls # According to DeepSeek API docs, reasoning_content is REQUIRED when tool_calls # are present during the tool invocation process (within same question/turn). # See: https://api-docs.deepseek.com/guides/thinking_mode#tool-calls reasoning_content_map = {} for i, msg in enumerate(messages): if ( isinstance(msg, AIMessage) and (msg.tool_calls or msg.invalid_tool_calls) and (reasoning := msg.additional_kwargs.get("reasoning_content")) ): reasoning_content_map[i] = reasoning payload = super()._get_request_payload(input_, stop=stop, **kwargs) # Restore reasoning_content for assistant messages with tool_calls # This is required by DeepSeek API - missing it causes 400 error if "messages" in payload and reasoning_content_map: for i, message in enumerate(payload["messages"]): if ( i in reasoning_content_map and message.get("role") == "assistant" and message.get("tool_calls") ): message["reasoning_content"] = reasoning_content_map[i] # Apply DeepSeek-specific message formatting for message in payload["messages"]: if message["role"] == "tool" and isinstance(message["content"], list): message["content"] = json.dumps(message["content"]) elif message["role"] == "assistant" and isinstance( message["content"], list ): # DeepSeek API expects assistant content to be a string, not a list. # Extract text blocks and join them, or use empty string if none exist. text_parts = [ block.get("text", "") for block in message["content"] if isinstance(block, dict) and block.get("type") == "text" ] message["content"] = "".join(text_parts) if text_parts else "" return payload ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DockerAIEmbeddingModelParams(BaseForm): dimensions = forms.SingleSelect( TooltipLabel( _('Dimensions'), _('') ), required=True, default_value=1024, value_field='value', text_field='label', option_list=[ {'label': '1536', 'value': '1536'}, {'label': '1024', 'value': '1024'}, {'label': '768', 'value': '768'}, {'label': '512', 'value': '512'}, ] ) class DockerAIEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return DockerAIEmbeddingModelParams() api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext class DockerAIImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class DockerAIImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return DockerAIImageModelParams() ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from openai import BadRequestError from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DockerAILLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class DockerAILLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException) or isinstance(e, BadRequestError): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return DockerAILLMModelParams() ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: reranker.py @date:2024/9/9 17:51 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.docker_ai_model_provider.model.reranker import DockerAIReranker from common.utils.logger import maxkb_logger class DockerAIRerankerCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): if not model_type == 'RERANKER': raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model: DockerAIReranker = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=_('Hello'))], _('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model} api_base = forms.TextInputField('API URL', required=True) ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DockerAISTTModelParams(BaseForm): language = forms.TextInputField( TooltipLabel(_('language'), _('If not passed, the default value is zh')), required=True, default_value='zh', ) class DockerAISTTModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return DockerAISTTModelParams() ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DockerAITTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), _(''' By default, images are produced in standard quality, but with DALL·E 3 you can set quality: "hd" to enhance detail. Square, standard quality images are generated fastest. ''')), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class DockerAITextToImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return DockerAITTIModelParams() ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class DockerAITTSModelGeneralParams(BaseForm): # alloy, echo, fable, onyx, nova, shimmer voice = forms.SingleSelect( TooltipLabel('Voice', _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')), required=True, default_value='alloy', text_field='value', value_field='value', option_list=[ {'text': 'alloy', 'value': 'alloy'}, {'text': 'echo', 'value': 'echo'}, {'text': 'fable', 'value': 'fable'}, {'text': 'onyx', 'value': 'onyx'}, {'text': 'nova', 'value': 'nova'}, {'text': 'shimmer', 'value': 'shimmer'}, ]) class DockerAITTSModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return DockerAITTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/docker_ai_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: docker_ai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.docker_ai_model_provider.credential.embedding import DockerAIEmbeddingCredential from models_provider.impl.docker_ai_model_provider.credential.image import DockerAIImageModelCredential from models_provider.impl.docker_ai_model_provider.credential.llm import DockerAILLMModelCredential from models_provider.impl.docker_ai_model_provider.credential.reranker import DockerAIRerankerCredential from models_provider.impl.docker_ai_model_provider.credential.stt import DockerAISTTModelCredential from models_provider.impl.docker_ai_model_provider.credential.tti import DockerAITextToImageModelCredential from models_provider.impl.docker_ai_model_provider.credential.tts import DockerAITTSModelCredential from models_provider.impl.docker_ai_model_provider.model.embedding import DockerAIEmbeddingModel from models_provider.impl.docker_ai_model_provider.model.image import DockerAIImage from models_provider.impl.docker_ai_model_provider.model.llm import DockerAIChatModel from models_provider.impl.docker_ai_model_provider.model.reranker import DockerAIReranker from models_provider.impl.docker_ai_model_provider.model.stt import DockerAISpeechToText from models_provider.impl.docker_ai_model_provider.model.tti import DockerAITextToImage from models_provider.impl.docker_ai_model_provider.model.tts import DockerAITextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext_lazy as _ docker_ai_llm_model_credential = DockerAILLMModelCredential() docker_ai_stt_model_credential = DockerAISTTModelCredential() docker_ai_tts_model_credential = DockerAITTSModelCredential() docker_ai_image_model_credential = DockerAIImageModelCredential() docker_ai_tti_model_credential = DockerAITextToImageModelCredential() model_info_list = [ ModelInfo('ai/qwen3-vl:8B', '', ModelTypeConst.LLM, docker_ai_llm_model_credential, DockerAIChatModel ), ] open_ai_embedding_credential = DockerAIEmbeddingCredential() model_info_embedding_list = [ ModelInfo('ai/qwen3-embedding-vllm', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, DockerAIEmbeddingModel), ] # model_info_image_list = [ # ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with DockerAI adjustments'), # ModelTypeConst.IMAGE, docker_ai_image_model_credential, # DockerAIImage), # ModelInfo('gpt-4o-mini', # _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with DockerAI adjustments'), # ModelTypeConst.IMAGE, docker_ai_image_model_credential, # DockerAIImage), # ] # model_info_tti_list = [ # ModelInfo('dall-e-3', '', # ModelTypeConst.TTI, docker_ai_tti_model_credential, # DockerAITextToImage), # ] docker_ai_reranker_model_credential = DockerAIRerankerCredential() model_info_rerank_list = [ ModelInfo('ai/qwen3-reranker:0.6B', '', ModelTypeConst.RERANKER, docker_ai_reranker_model_credential, DockerAIReranker), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info( ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with DockerAI adjustments'), ModelTypeConst.LLM, docker_ai_llm_model_credential, DockerAIChatModel )) .append_model_info_list(model_info_embedding_list) .append_default_model_info(model_info_embedding_list[0]) # .append_model_info_list(model_info_image_list) # .append_default_model_info(model_info_image_list[0]) # .append_model_info_list(model_info_tti_list) # .append_default_model_info(model_info_tti_list[0]) # .append_default_model_info(ModelInfo('whisper-1', '', # ModelTypeConst.STT, docker_ai_stt_model_credential, # DockerAISpeechToText) # ) # .append_default_model_info(ModelInfo('tts-1', '', # ModelTypeConst.TTS, docker_ai_tts_model_credential, # DockerAITextToSpeech)) .append_model_info_list(model_info_rerank_list) .append_default_model_info(model_info_rerank_list[0]) .build() ) class DockerModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_docker_ai_provider', name='Docker AI', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'docker_ai_model_provider', 'icon', 'docker_ai_icon_svg'))) ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict, List import openai from models_provider.base_model_provider import MaxKBBaseModel class DockerAIEmbeddingModel(MaxKBBaseModel): model_name: str optional_params: dict def __init__(self, api_key, base_url, model_name: str, optional_params: dict): self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings self.model_name = model_name self.optional_params = optional_params def is_cache_model(self): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return DockerAIEmbeddingModel( api_key=model_credential.get('api_key'), model_name=model_name, base_url=model_credential.get('api_base'), optional_params=optional_params ) def embed_query(self, text: str): res = self.embed_documents([text]) return res[0] def embed_documents( self, texts: List[str], chunk_size: int | None = None ) -> List[List[float]]: if len(self.optional_params) > 0: res = self.client.create( input=texts, model=self.model_name, encoding_format="float", **self.optional_params ) else: res = self.client.create(input=texts, model=self.model_name, encoding_format="float") return [e.embedding for e in res.data] ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class DockerAIImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return DockerAIImage( model_name=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import List, Dict from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class DockerAIChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) streaming = model_kwargs.get('streaming', True) if 'o1' in model_name: streaming = False chat_open_ai = DockerAIChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params, streaming=streaming, custom_get_token_ids=custom_get_token_ids ) return chat_open_ai def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: siliconcloud_reranker.py @date:2024/9/10 9:45 @desc: SiliconCloud 文档重排封装 """ import json from typing import Sequence, Optional, Any, Dict import requests from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from models_provider.base_model_provider import MaxKBBaseModel class DockerAIReranker(MaxKBBaseModel, BaseDocumentCompressor): api_base: Optional[str] model: Optional[str] top_n: Optional[int] = 3 # 取前 N 个最相关的结果 @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return DockerAIReranker( api_base=model_credential.get('api_base'), model=model_name, top_n=model_kwargs.get('top_n', 3) ) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if not documents: return [] # 预处理文本 texts = [doc.page_content for doc in documents] headers = { "Content-Type": "application/json" } payload = { "model": self.model, "query": query, "documents": texts, "top_n": self.top_n, } response = requests.post(f"{self.api_base}/rerank", data=json.dumps(payload), headers=headers) if response.status_code != 200: raise RuntimeError(f"Docker AI API 请求失败: {response.text}") res = response.json() # 解析返回结果 return [ Document( page_content=payload['documents'][item.get('index')], metadata={'relevance_score': item.get('relevance_score')} ) for item in res.get('results', []) ] ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/stt.py ================================================ import asyncio import io from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class DockerAISpeechToText(MaxKBBaseModel, BaseSpeechToText): api_base: str api_key: str model: str params: dict @staticmethod def is_cache_model(): return False def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return DockerAISpeechToText( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), params = model_kwargs, **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def speech_to_text(self, audio_file): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) audio_data = audio_file.read() buffer = io.BytesIO(audio_data) buffer.name = "file.mp3" # this is the important line filter_params = {k: v for k,v in self.params.items() if k not in {'model_id','use_local','streaming'}} transcription_params = { 'model': self.model, 'file': buffer, 'language': 'zh' } res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params) return res.text ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/tti.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class DockerAITextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return DockerAITextToImage( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) response_list = chat.models.with_raw_response.list() # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) res = chat.images.generate(model=self.model, prompt=prompt, **self.params) file_urls = [] try: for content in res.data: if content.url: file_urls.append(content.url) elif content.b64_json: file_urls.append(content.b64_json) return file_urls except Exception as e: raise e ================================================ FILE: apps/models_provider/impl/docker_ai_model_provider/model/tts.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class DockerAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': 'alloy'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return DockerAITextToSpeech( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def text_to_speech(self, text): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) text = _remove_empty_lines(text) with client.audio.speech.with_streaming_response.create( model=self.model, input=text, **self.params ) as response: return response.read() ================================================ FILE: apps/models_provider/impl/gemini_model_provider/__init__.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :__init__.py.py @Author :Brian Yang @Date :5/13/24 7:40 AM """ ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class GeminiEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class GeminiImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class GeminiImageModelCredential(BaseForm, BaseModelCredential): api_key = forms.PasswordInputField('API Key', required=True) base_url = forms.TextInputField('Base URL', required=True, default_value='https://generativelanguage.googleapis.com') def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'base_url']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return GeminiImageModelParams() ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 17:57 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class GeminiLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class GeminiLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'base_url']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_key = forms.PasswordInputField('API Key', required=True) base_url = forms.TextInputField('Base URL', required=True, default_value='https://generativelanguage.googleapis.com') def get_model_params_setting_form(self, model_name): return GeminiLLMModelParams() ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class GeminiSTTModelCredential(BaseForm, BaseModelCredential): api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/gemini_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class GeminiTTIModelParams(BaseForm): pass class GeminiTextToImageModelCredential(BaseForm, BaseModelCredential): base_url = forms.TextInputField('Base Url', required=True, default_value='https://generativelanguage.googleapis.com') api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['base_url', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return GeminiTTIModelParams() ================================================ FILE: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :gemini_model_provider.py @Author :Brian Yang @Date :5/13/24 7:47 AM """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ ModelInfoManage from models_provider.impl.gemini_model_provider.credential.embedding import GeminiEmbeddingCredential from models_provider.impl.gemini_model_provider.credential.image import GeminiImageModelCredential from models_provider.impl.gemini_model_provider.credential.llm import GeminiLLMModelCredential from models_provider.impl.gemini_model_provider.credential.stt import GeminiSTTModelCredential from models_provider.impl.gemini_model_provider.credential.tti import GeminiTextToImageModelCredential from models_provider.impl.gemini_model_provider.model.embedding import GeminiEmbeddingModel from models_provider.impl.gemini_model_provider.model.image import GeminiImage from models_provider.impl.gemini_model_provider.model.llm import GeminiChatModel from models_provider.impl.gemini_model_provider.model.stt import GeminiSpeechToText from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ from models_provider.impl.gemini_model_provider.model.tti import GeminiTextToImage gemini_llm_model_credential = GeminiLLMModelCredential() gemini_image_model_credential = GeminiImageModelCredential() gemini_stt_model_credential = GeminiSTTModelCredential() gemini_embedding_model_credential = GeminiEmbeddingCredential() gemini_tti_model_credential = GeminiTextToImageModelCredential() model_info_list = [ ModelInfo('gemini-1.0-pro', _('Latest Gemini 1.0 Pro model, updated with Google update'), ModelTypeConst.LLM, gemini_llm_model_credential, GeminiChatModel), ModelInfo('gemini-1.0-pro-vision', _('Latest Gemini 1.0 Pro Vision model, updated with Google update'), ModelTypeConst.LLM, gemini_llm_model_credential, GeminiChatModel), ] model_image_info_list = [ ModelInfo('gemini-1.5-flash', _('Latest Gemini 1.5 Flash model, updated with Google updates'), ModelTypeConst.IMAGE, gemini_image_model_credential, GeminiImage), ModelInfo('gemini-1.5-pro', _('Latest Gemini 1.5 Flash model, updated with Google updates'), ModelTypeConst.IMAGE, gemini_image_model_credential, GeminiImage), ] model_stt_info_list = [ ModelInfo('gemini-1.5-flash', _('Latest Gemini 1.5 Flash model, updated with Google updates'), ModelTypeConst.STT, gemini_stt_model_credential, GeminiSpeechToText), ModelInfo('gemini-1.5-pro', _('Latest Gemini 1.5 Flash model, updated with Google updates'), ModelTypeConst.STT, gemini_stt_model_credential, GeminiSpeechToText), ] model_embedding_info_list = [ ModelInfo('models/embedding-001', '', ModelTypeConst.EMBEDDING, gemini_embedding_model_credential, GeminiEmbeddingModel), ModelInfo('models/text-embedding-004', '', ModelTypeConst.EMBEDDING, gemini_embedding_model_credential, GeminiEmbeddingModel), ] model_tti_info_list = [ ModelInfo('gemini-3.1-flash-image-preview', "", ModelTypeConst.TTI, gemini_tti_model_credential, GeminiTextToImage) ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_model_info_list(model_image_info_list) .append_model_info_list(model_stt_info_list) .append_model_info_list(model_embedding_info_list) .append_model_info_list(model_tti_info_list) .append_default_model_info(model_info_list[0]) .append_default_model_info(model_image_info_list[0]) .append_default_model_info(model_stt_info_list[0]) .append_default_model_info(model_embedding_info_list[0]) .append_default_model_info(model_tti_info_list[0]) .build() ) class GeminiModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_gemini_provider', name='Gemini', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'gemini_model_provider', 'icon', 'gemini_icon_svg'))) ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict from langchain_google_genai import GoogleGenerativeAIEmbeddings from models_provider.base_model_provider import MaxKBBaseModel class GeminiEmbeddingModel(MaxKBBaseModel, GoogleGenerativeAIEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return GeminiEmbeddingModel( google_api_key=model_credential.get('api_key'), model=model_name, ) ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/image.py ================================================ from typing import Dict from langchain_google_genai import ChatGoogleGenerativeAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class GeminiImage(MaxKBBaseModel, ChatGoogleGenerativeAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) base_url = model_credential.get('base_url', "https://generativelanguage.googleapis.com") if base_url: optional_params.setdefault("model_kwargs", {}) optional_params["model_kwargs"]["http_options"] = {"base_url": base_url} return GeminiImage( model=model_name, api_key=model_credential.get('api_key'), **optional_params, ) ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/llm.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :llm.py @Author :Brian Yang @Date :5/13/24 7:40 AM """ from typing import List, Dict, Optional, Any from langchain_core.messages import BaseMessage, get_buffer_string from langchain_google_genai import ChatGoogleGenerativeAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel class GeminiChatModel(MaxKBBaseModel, ChatGoogleGenerativeAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) base_url = model_credential.get('base_url', "https://generativelanguage.googleapis.com") if base_url: optional_params.setdefault("model_kwargs", {}) optional_params["model_kwargs"]["http_options"] = {"base_url": base_url} gemini_chat = GeminiChatModel( model=model_name, api_key=model_credential.get('api_key'), **optional_params ) return gemini_chat def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.__dict__.get('_last_generation_info') def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return self.get_last_generation_info().get('input_tokens', 0) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return self.get_last_generation_info().get('output_tokens', 0) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/stt.py ================================================ from typing import Dict from django.utils.translation import gettext as _ from langchain_core.messages import HumanMessage from langchain_google_genai import ChatGoogleGenerativeAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class GeminiSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_key: str model: str def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return GeminiSpeechToText( model=model_name, api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): client = ChatGoogleGenerativeAI( model=self.model, google_api_key=self.api_key ) response_list = client.invoke(_('Hello')) # print(response_list) def speech_to_text(self, audio_file): client = ChatGoogleGenerativeAI( model=self.model, google_api_key=self.api_key ) audio_data = audio_file.read() system_instruction = """You are a professional speech-to-text assistant. Your task is to: 1. Transcribe the audio content accurately into text 2. Output ONLY the transcribed text without any additional comments...""" msg = HumanMessage(content=[ {'type': 'text', 'text': system_instruction}, {"type": "media", 'mime_type': 'audio/mp3', "data": audio_data} ]) res = client.invoke([msg]) if isinstance(res.content, list): for item in res.content: if isinstance(item, dict) and 'text' in item: return item['text'].strip() elif hasattr(item, 'text'): return item.text.strip() return '' elif isinstance(res.content, dict): return res.content.get('text', '').strip() else: return str(res.content).strip() if res.content else '' ================================================ FILE: apps/models_provider/impl/gemini_model_provider/model/tti.py ================================================ import base64 from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class GeminiTextToImage(MaxKBBaseModel, BaseTextToImage): base_url: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.base_url = kwargs.get('base_url') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return GeminiTextToImage( model=model_name, base_url=model_credential.get('base_url', "https://generativelanguage.googleapis.com"), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): return True def generate_image(self, prompt: str, negative_prompt: str = None): from google import genai from google.genai import types from PIL import Image file_urls = [] client = genai.Client(api_key=self.api_key, http_options={"base_url": self.base_url}, **self.params) if self.model.startswith('imagen'): config = types.GenerateImagesConfig(**self.params) # 如果有 negative_prompt 就加入 if negative_prompt: config.negative_prompt = negative_prompt response = client.models.generate_images( model=self.model, prompt=prompt, config=config ) for generated_image in response.generated_images: img_base64 = base64.b64encode(generated_image.image.image_bytes).decode("utf-8") file_urls.append(f'data:{generated_image.image.mime_type};base64,{img_base64}') else: config = types.GenerateContentConfig(**self.params) if negative_prompt: config.negative_prompt = negative_prompt response = client.models.generate_content( model=self.model, contents=[prompt], config=config ) for part in response.parts: if part.text is not None: print(part.text) elif part.inline_data is not None: image_bytes = part.inline_data.data img_base64 = base64.b64encode(image_bytes).decode("utf-8") file_urls.append(f'data:{part.inline_data.mime_type};base64,{img_base64}') return file_urls ================================================ FILE: apps/models_provider/impl/kimi_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2023/10/31 17:16 @desc: """ ================================================ FILE: apps/models_provider/impl/kimi_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/kimi_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:06 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class KimiLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.3, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class KimiLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return KimiLLMModelParams() ================================================ FILE: apps/models_provider/impl/kimi_model_provider/kimi_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: kimi_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.kimi_model_provider.credential.llm import KimiLLMModelCredential from models_provider.impl.kimi_model_provider.model.llm import KimiChatModel from maxkb.conf import PROJECT_DIR kimi_llm_model_credential = KimiLLMModelCredential() moonshot_v1_8k = ModelInfo('moonshot-v1-8k', '', ModelTypeConst.LLM, kimi_llm_model_credential, KimiChatModel) moonshot_v1_32k = ModelInfo('moonshot-v1-32k', '', ModelTypeConst.LLM, kimi_llm_model_credential, KimiChatModel) moonshot_v1_128k = ModelInfo('moonshot-v1-128k', '', ModelTypeConst.LLM, kimi_llm_model_credential, KimiChatModel) model_info_manage = ModelInfoManage.builder().append_model_info(moonshot_v1_8k).append_model_info( moonshot_v1_32k).append_default_model_info(moonshot_v1_128k).append_default_model_info(moonshot_v1_8k).build() class KimiModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_dialogue_number(self): return 3 def get_model_provide_info(self): return ModelProvideInfo(provider='model_kimi_provider', name='Kimi', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'kimi_model_provider', 'icon', 'kimi_icon_svg'))) ================================================ FILE: apps/models_provider/impl/kimi_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/kimi_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2023/11/10 17:45 @desc: """ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class KimiChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) kimi_chat_open_ai = KimiChatModel( openai_api_base=model_credential['api_base'], openai_api_key=model_credential['api_key'], model=model_name, extra_body=optional_params, ) return kimi_chat_open_ai ================================================ FILE: apps/models_provider/impl/local_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py @date:2024/7/10 17:48 @desc: """ ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/embedding/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/7 14:02 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/embedding/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py.py @date:2025/11/7 14:02 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.local_model_provider.model.embedding import LocalEmbedding from common.utils.logger import maxkb_logger class LocalEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): if not model_type == 'EMBEDDING': raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['cache_folder']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential) model.embed_query(gettext('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return model cache_folder = forms.TextInputField(_('Model catalog'), required=True) ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/embedding/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/7 14:03 @desc: """ from typing import Dict import requests from django.utils.translation import gettext_lazy as _ from common import forms from common.forms import BaseForm from maxkb.const import CONFIG from models_provider.base_model_provider import BaseModelCredential class LocalEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' prefix = CONFIG.get_admin_path() res = requests.post( f'{CONFIG.get("LOCAL_MODEL_PROTOCOL")}://{bind}{prefix}/api/model/validate', json={'model_name': model_name, 'model_type': model_type, 'model_credential': model_credential}) result = res.json() if result.get('code', 500) == 200: return result.get('data') raise Exception(result.get('message')) def encryption_dict(self, model: Dict[str, object]): return model cache_folder = forms.TextInputField(_('Model catalog'), required=True) ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/reranker/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/7 14:22 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/reranker/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py @date:2025/11/7 14:23 @desc: """ from typing import Dict from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.local_model_provider.model.reranker import LocalReranker from django.utils.translation import gettext_lazy as _, gettext from common.utils.logger import maxkb_logger class LocalRerankerCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): if not model_type == 'RERANKER': raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['cache_dir']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model: LocalReranker = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=gettext('Hello'))], gettext('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return model cache_dir = forms.TextInputField(_('Model catalog'), required=True) ================================================ FILE: apps/models_provider/impl/local_model_provider/credential/reranker/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/7 14:23 @desc: """ from typing import Dict import requests from django.utils.translation import gettext_lazy as _ from common import forms from common.forms import BaseForm from maxkb.const import CONFIG from models_provider.base_model_provider import BaseModelCredential class LocalRerankerCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' prefix = CONFIG.get_admin_path() res = requests.post( f'{CONFIG.get("LOCAL_MODEL_PROTOCOL")}://{bind}{prefix}/api/model/validate', json={'model_name': model_name, 'model_type': model_type, 'model_credential': model_credential}) result = res.json() if result.get('code', 500) == 200: return result.get('data') raise Exception(result.get('message')) def encryption_dict(self, model: Dict[str, object]): return model cache_dir = forms.TextInputField(_('Model catalog'), required=True) ================================================ FILE: apps/models_provider/impl/local_model_provider/local_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: zhipu_model_provider.py @date:2024/04/19 13:5 @desc: """ import os from django.utils.translation import gettext as _ from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR from models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \ ModelInfoManage from models_provider.impl.local_model_provider.credential.embedding import LocalEmbeddingCredential from models_provider.impl.local_model_provider.credential.reranker import LocalRerankerCredential from models_provider.impl.local_model_provider.model.embedding import LocalEmbedding from models_provider.impl.local_model_provider.model.reranker import LocalReranker embedding_text2vec_base_chinese = ModelInfo('shibing624/text2vec-base-chinese', '', ModelTypeConst.EMBEDDING, LocalEmbeddingCredential(), LocalEmbedding) bge_reranker_v2_m3 = ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER, LocalRerankerCredential(), LocalReranker) model_info_manage = (ModelInfoManage.builder().append_model_info(embedding_text2vec_base_chinese) .append_default_model_info(embedding_text2vec_base_chinese) .append_model_info(bge_reranker_v2_m3) .append_default_model_info(bge_reranker_v2_m3) .build()) class LocalModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_local_provider', name=_('local model'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'local_model_provider', 'icon', 'local_icon_svg'))) ================================================ FILE: apps/models_provider/impl/local_model_provider/model/embedding/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/11/5 15:24 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/models_provider/impl/local_model_provider/model/embedding/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py @date:2025/11/5 15:26 @desc: """ from typing import Dict from langchain_huggingface import HuggingFaceEmbeddings from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel max_retries = 3 class LocalEmbedding(MaxKBBaseModel, HuggingFaceEmbeddings): @staticmethod def is_cache_model(): return True @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): for attempt in range(max_retries): try: embedding = LocalEmbedding(model_name=model_name, cache_folder=model_credential.get('cache_folder'), model_kwargs={'device': model_credential.get('device')}, encode_kwargs={'normalize_embeddings': True} ) # 测试一下是否真的能用 embedding.embed_query("test") return embedding except Exception as e: if 'meta tensor' in str(e).lower() and attempt < max_retries - 1: maxkb_logger.warning( f"Test failed with meta tensor error, retrying... (attempt {attempt + 1}/{max_retries})") import time time.sleep(1) continue raise e ================================================ FILE: apps/models_provider/impl/local_model_provider/model/embedding/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/5 15:24 @desc: """ from typing import Dict, List import requests from anthropic import BaseModel from langchain_core.embeddings import Embeddings from maxkb.const import CONFIG from models_provider.base_model_provider import MaxKBBaseModel class LocalEmbedding(MaxKBBaseModel, BaseModel, Embeddings): def __init__(self, **kwargs): super().__init__(**kwargs) self.model_id = kwargs.get('model_id', None) @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return LocalEmbedding(model_name=model_name, cache_folder=model_credential.get('cache_folder'), model_kwargs={'device': model_credential.get('device')}, encode_kwargs={'normalize_embeddings': True}, **model_kwargs) model_id: str = None def embed_query(self, text: str) -> List[float]: bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' prefix = CONFIG.get_admin_path() res = requests.post( f'{CONFIG.get("LOCAL_MODEL_PROTOCOL")}://{bind}{prefix}/api/model/{self.model_id}/embed_query', {'text': text}) result = res.json() if result.get('code', 500) == 200: return result.get('data') raise Exception(result.get('message')) def embed_documents(self, texts: List[str]) -> List[List[float]]: bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' prefix = CONFIG.get_admin_path() res = requests.post( f'{CONFIG.get("LOCAL_MODEL_PROTOCOL")}://{bind}/{prefix}/api/model/{self.model_id}/embed_documents', {'texts': texts}) result = res.json() if result.get('code', 500) == 200: return result.get('data') raise Exception(result.get('message')) ================================================ FILE: apps/models_provider/impl/local_model_provider/model/reranker/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/11/5 15:30 @desc: """ import os if os.environ.get('SERVER_NAME', 'web') == 'local_model': from .model import * else: from .web import * ================================================ FILE: apps/models_provider/impl/local_model_provider/model/reranker/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: model.py @date:2025/11/5 15:30 @desc: """ from typing import Sequence, Optional, Dict, Any from langchain_core.callbacks import Callbacks from langchain_core.documents import Document, BaseDocumentCompressor from models_provider.base_model_provider import MaxKBBaseModel class LocalReranker(MaxKBBaseModel, BaseDocumentCompressor): client: Any = None tokenizer: Any = None model: Optional[str] = None cache_dir: Optional[str] = None model_kwargs: Any = {} def __init__(self, model_name, cache_dir=None, **model_kwargs): super().__init__() from transformers import AutoModelForSequenceClassification, AutoTokenizer self.model = model_name self.cache_dir = cache_dir self.model_kwargs = model_kwargs self.client = AutoModelForSequenceClassification.from_pretrained(self.model, cache_dir=self.cache_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model, cache_dir=self.cache_dir) self.client = self.client.to(self.model_kwargs.get('device', 'cpu')) self.client.eval() @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return LocalReranker(model_name, cache_dir=model_credential.get('cache_dir')) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if documents is None or len(documents) == 0: return [] import torch with torch.no_grad(): inputs = self.tokenizer([[query, document.page_content] for document in documents], padding=True, truncation=True, return_tensors='pt', max_length=512) scores = [torch.sigmoid(s).float().item() for s in self.client(**inputs, return_dict=True).logits.view(-1, ).float()] result = [Document(page_content=documents[index].page_content, metadata={'relevance_score': scores[index]}) for index in range(len(documents))] result.sort(key=lambda row: row.metadata.get('relevance_score'), reverse=True) return result ================================================ FILE: apps/models_provider/impl/local_model_provider/model/reranker/web.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: web.py @date:2025/11/5 15:30 @desc: """ from typing import Sequence, Optional, Dict import requests from anthropic import BaseModel from langchain_core.callbacks import Callbacks from langchain_core.documents import Document, BaseDocumentCompressor from maxkb.const import CONFIG from models_provider.base_model_provider import MaxKBBaseModel class LocalReranker(MaxKBBaseModel, BaseModel, BaseDocumentCompressor): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return LocalReranker(model_type=model_type, model_name=model_name, model_credential=model_credential, **model_kwargs) model_id: str = None def __init__(self, **kwargs): super().__init__(**kwargs) self.model_id = kwargs.get('model_id', None) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if documents is None or len(documents) == 0: return [] prefix = CONFIG.get_admin_path() bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' res = requests.post( f'{CONFIG.get("LOCAL_MODEL_PROTOCOL")}://{bind}{prefix}/api/model/{self.model_id}/compress_documents', json={'documents': [{'page_content': document.page_content, 'metadata': document.metadata} for document in documents], 'query': query}, headers={'Content-Type': 'application/json'}) result = res.json() if result.get('code', 500) == 200: return [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document in result.get('data')] raise Exception(result.get('message')) ================================================ FILE: apps/models_provider/impl/ollama_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/5 17:20 @desc: """ ================================================ FILE: apps/models_provider/impl/ollama_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/ollama_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 15:10 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.local_model_provider.model.embedding import LocalEmbedding class OllamaEmbeddingModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid')) exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if model.get('model') == model_name or model.get('model').replace(":latest", "") == model_name] if len(exist) == 0: raise AppApiException(ValidCode.model_not_fount, _('The model does not exist, please download the model first')) model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) return True def encryption_dict(self, model_info: Dict[str, object]): return model_info def build_model(self, model_info: Dict[str, object]): for key in ['model']: if key not in model_info: raise AppApiException(500, _('{key} is required').format(key=key)) return self api_base = forms.TextInputField('API URL', required=True) ================================================ FILE: apps/models_provider/impl/ollama_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext class OllamaImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class OllamaImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid')) exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if model.get('model') == model_name or model.get('model').replace(":latest", "") == model_name] if len(exist) == 0: raise AppApiException(ValidCode.model_not_fount, gettext('The model does not exist, please download the model first')) return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OllamaImageModelParams() ================================================ FILE: apps/models_provider/impl/ollama_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:19 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class OllamaLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.3, _min=0.1, _max=1.0, _step=0.01, precision=2) num_predict = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class OllamaLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid')) exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if model.get('model') == model_name or model.get('model').replace(":latest", "") == model_name] if len(exist) == 0: raise AppApiException(ValidCode.model_not_fount, gettext('The model does not exist, please download the model first')) return True def encryption_dict(self, model_info: Dict[str, object]): return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} def build_model(self, model_info: Dict[str, object]): for key in ['api_key', 'model']: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_key = model_info.get('api_key') return self api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return OllamaLLMModelParams() ================================================ FILE: apps/models_provider/impl/ollama_model_provider/credential/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 15:10 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.ollama_model_provider.model.reranker import OllamaReranker from langchain_core.documents import BaseDocumentCompressor, Document from django.utils.translation import gettext_lazy as _, gettext class OllamaReRankModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): if not model_type == 'RERANKER': raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid')) exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if model.get('model') == model_name or model.get('model').replace(":latest", "") == model_name] if len(exist) == 0: raise AppApiException(ValidCode.model_not_fount, _('The model does not exist, please download the model first')) try: model: OllamaReranker = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=gettext('Hello'))], gettext('Hello')) except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model_info: Dict[str, object]): return model_info def build_model(self, model_info: Dict[str, object]): for key in ['model']: if key not in model_info: raise AppApiException(500, _('{key} is required').format(key=key)) return self api_base = forms.TextInputField('API URL', required=True) ================================================ FILE: apps/models_provider/impl/ollama_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/ollama_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 15:02 @desc: """ from typing import Dict, List from langchain_ollama import OllamaEmbeddings from models_provider.base_model_provider import MaxKBBaseModel class OllamaEmbedding(MaxKBBaseModel, OllamaEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return OllamaEmbedding( model=model_name, base_url=model_credential.get('api_base'), ) def embed_documents(self, texts: List[str]) -> List[List[float]]: """Embed documents using an Ollama deployed embedding model. Args: texts: The list of texts to embed. Returns: List of embeddings, one for each text. """ return self._client.embed( self.model, texts, options=self._default_params, keep_alive=self.keep_alive )["embeddings"] def embed_query(self, text: str) -> List[float]: """Embed a query using a Ollama deployed embedding model. Args: text: The text to embed. Returns: Embeddings for the text. """ return self.embed_documents([text])[0] ================================================ FILE: apps/models_provider/impl/ollama_model_provider/model/image.py ================================================ from typing import Dict from urllib.parse import urlparse, ParseResult from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class OllamaImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): api_base = model_credential.get('api_base', '') base_url = get_base_url(api_base) base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1') optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return OllamaImage( model_name=model_name, openai_api_base=base_url, openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/ollama_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/3/6 11:48 @desc: """ from typing import List, Dict from urllib.parse import urlparse, ParseResult from langchain_core.messages import BaseMessage, get_buffer_string from langchain_ollama.chat_models import ChatOllama from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class OllamaChatModel(MaxKBBaseModel, ChatOllama): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): api_base = model_credential.get('api_base', '') base_url = get_base_url(api_base) optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return OllamaChatModel(model=model_name, base_url=base_url, stream=True, **optional_params) def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/ollama_model_provider/model/reranker.py ================================================ from typing import Sequence, Optional, Dict from langchain_ollama import OllamaEmbeddings from langchain_core.callbacks import Callbacks from langchain_core.documents import Document from pydantic import BaseModel, Field from models_provider.base_model_provider import MaxKBBaseModel class OllamaReranker(MaxKBBaseModel, OllamaEmbeddings, BaseModel): top_n: Optional[int] = Field(3, description="Number of top documents to return") @staticmethod 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]: from sklearn.metrics.pairwise import cosine_similarity """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 ] ================================================ FILE: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: ollama_model_provider.py @date:2024/3/5 17:23 @desc: """ import json import os from typing import Dict, Iterator from urllib.parse import urlparse, ParseResult import requests from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ BaseModelCredential, DownModelChunk, DownModelChunkStatus, ValidCode, ModelInfoManage from models_provider.impl.ollama_model_provider.credential.embedding import OllamaEmbeddingModelCredential from models_provider.impl.ollama_model_provider.credential.image import OllamaImageModelCredential from models_provider.impl.ollama_model_provider.credential.llm import OllamaLLMModelCredential from models_provider.impl.ollama_model_provider.credential.reranker import OllamaReRankModelCredential from models_provider.impl.ollama_model_provider.model.embedding import OllamaEmbedding from models_provider.impl.ollama_model_provider.model.image import OllamaImage from models_provider.impl.ollama_model_provider.model.llm import OllamaChatModel from models_provider.impl.ollama_model_provider.model.reranker import OllamaReranker from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ "" ollama_llm_model_credential = OllamaLLMModelCredential() model_info_list = [ ModelInfo( 'deepseek-r1:1.5b', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'deepseek-r1:7b', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'deepseek-r1:8b', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'deepseek-r1:14b', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'deepseek-r1:32b', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama2', _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 7B pretrained models. Links to other models can be found in the index at the bottom.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama2:13b', _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 13B pretrained models. Links to other models can be found in the index at the bottom.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama2:70b', _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 70B pretrained models. Links to other models can be found in the index at the bottom.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama2-chinese:13b', _('Since the Chinese alignment of Llama2 itself is weak, we use the Chinese instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so that it has strong Chinese conversation capabilities.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama3:8b', _('Meta Llama 3: The most capable public product LLM to date. 8 billion parameters.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'llama3:70b', _('Meta Llama 3: The most capable public product LLM to date. 70 billion parameters.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:0.5b', _("Compared with previous versions, qwen 1.5 0.5b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 500 million parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:1.8b', _("Compared with previous versions, qwen 1.5 1.8b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 1.8 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:4b', _("Compared with previous versions, qwen 1.5 4b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 4 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:7b', _("Compared with previous versions, qwen 1.5 7b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 7 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:14b', _("Compared with previous versions, qwen 1.5 14b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 14 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:32b', _("Compared with previous versions, qwen 1.5 32b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 32 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:72b', _("Compared with previous versions, qwen 1.5 72b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 72 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen:110b', _("Compared with previous versions, qwen 1.5 110b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 110 billion parameters."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2:72b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2:57b-a14b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2:7b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:72b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:32b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:14b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:7b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:1.5b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:0.5b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'qwen2.5:3b-instruct', '', ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ModelInfo( 'phi3', _("Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open model."), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel), ] ollama_embedding_model_credential = OllamaEmbeddingModelCredential() ollama_image_model_credential = OllamaImageModelCredential() ollama_reranker_model_credential = OllamaReRankModelCredential() embedding_model_info = [ ModelInfo( 'nomic-embed-text', _('A high-performance open embedding model with a large token context window.'), ModelTypeConst.EMBEDDING, ollama_embedding_model_credential, OllamaEmbedding), ] reranker_model_info = [ ModelInfo( 'linux6200/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER, ollama_reranker_model_credential, OllamaReranker), ] image_model_info = [ ModelInfo( 'llava:7b', '', ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage), ModelInfo( 'llava:13b', '', ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage), ModelInfo( 'llava:34b', '', ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_model_info_list(embedding_model_info) .append_default_model_info(ModelInfo( 'phi3', _('Phi-3 Mini is Microsoft\'s 3.8B parameter, lightweight, state-of-the-art open model.'), ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel)) .append_default_model_info(ModelInfo( 'nomic-embed-text', _('A high-performance open embedding model with a large token context window.'), ModelTypeConst.EMBEDDING, ollama_embedding_model_credential, OllamaEmbedding), ) .append_model_info_list(image_model_info) .append_default_model_info(image_model_info[0]) .append_model_info_list(reranker_model_info) .append_default_model_info(reranker_model_info[0]) .build() ) def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url def convert_to_down_model_chunk(row_str: str, chunk_index: int): row = json.loads(row_str) status = DownModelChunkStatus.unknown digest = "" progress = 100 if 'status' in row: digest = row.get('status') if row.get('status') == 'success': status = DownModelChunkStatus.success if row.get('status').__contains__("pulling"): progress = 0 status = DownModelChunkStatus.pulling if 'total' in row and 'completed' in row: progress = (row.get('completed') / row.get('total') * 100) elif 'error' in row: status = DownModelChunkStatus.error digest = row.get('error') return DownModelChunk(status=status, digest=digest, progress=progress, details=row_str, index=chunk_index) def convert(response_stream) -> Iterator[DownModelChunk]: temp = "" index = 0 for c in response_stream: index += 1 row_content = c.decode() temp += row_content if row_content.endswith('}') or row_content.endswith('\n'): rows = [t for t in temp.split("\n") if len(t) > 0] for row in rows: yield convert_to_down_model_chunk(row, index) temp = "" if len(temp) > 0: rows = [t for t in temp.split("\n") if len(t) > 0] for row in rows: yield convert_to_down_model_chunk(row, index) class OllamaModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_ollama_provider', name='Ollama', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'ollama_model_provider', 'icon', 'ollama_icon_svg'))) @staticmethod def get_base_model_list(api_base): base_url = get_base_url(api_base) r = requests.request(method="GET", url=f"{base_url}/api/tags", timeout=5) r.raise_for_status() return r.json() def down_model(self, model_type: str, model_name, model_credential: Dict[str, object]) -> Iterator[DownModelChunk]: api_base = model_credential.get('api_base', '') base_url = get_base_url(api_base) r = requests.request( method="POST", url=f"{base_url}/api/pull", data=json.dumps({"name": model_name}).encode(), stream=True, ) return convert(r) ================================================ FILE: apps/models_provider/impl/openai_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class OpenAIEmbeddingModelParams(BaseForm): dimensions = forms.SingleSelect( TooltipLabel( _('Dimensions'), _('') ), required=True, default_value=1024, value_field='value', text_field='label', option_list=[ {'label': '1536', 'value': '1536'}, {'label': '1024', 'value': '1024'}, {'label': '768', 'value': '768'}, {'label': '512', 'value': '512'}, ] ) class OpenAIEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OpenAIEmbeddingModelParams() api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext class OpenAIImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class OpenAIImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OpenAIImageModelParams() ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from openai import BadRequestError from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class OpenAILLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class OpenAILLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException) or isinstance(e, BadRequestError): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return OpenAILLMModelParams() ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class OpenAISTTModelParams(BaseForm): language = forms.TextInputField( TooltipLabel(_('language'), _('If not passed, the default value is zh')), required=True, default_value='zh', ) class OpenAISTTModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OpenAISTTModelParams() ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class OpenAITTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), _(''' By default, images are produced in standard quality, but with DALL·E 3 you can set quality: "hd" to enhance detail. Square, standard quality images are generated fastest. ''')), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class OpenAITextToImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OpenAITTIModelParams() ================================================ FILE: apps/models_provider/impl/openai_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class OpenAITTSModelGeneralParams(BaseForm): # alloy, echo, fable, onyx, nova, shimmer voice = forms.SingleSelect( TooltipLabel('Voice', _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')), required=True, default_value='alloy', text_field='value', value_field='value', option_list=[ {'text': 'alloy', 'value': 'alloy'}, {'text': 'echo', 'value': 'echo'}, {'text': 'fable', 'value': 'fable'}, {'text': 'onyx', 'value': 'onyx'}, {'text': 'nova', 'value': 'nova'}, {'text': 'shimmer', 'value': 'shimmer'}, ]) class OpenAITTSModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return OpenAITTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict, List import openai from models_provider.base_model_provider import MaxKBBaseModel class OpenAIEmbeddingModel(MaxKBBaseModel): model_name: str optional_params: dict def __init__(self, api_key, base_url, model_name: str, optional_params: dict): self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings self.model_name = model_name self.optional_params = optional_params def is_cache_model(self): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return OpenAIEmbeddingModel( api_key=model_credential.get('api_key'), model_name=model_name, base_url=model_credential.get('api_base'), optional_params=optional_params ) def embed_query(self, text: str): res = self.embed_documents([text]) return res[0] def embed_documents( self, texts: List[str], chunk_size: int | None = None ) -> List[List[float]]: if len(self.optional_params) > 0: res = self.client.create( input=texts, model=self.model_name, encoding_format="float", **self.optional_params ) else: res = self.client.create(input=texts, model=self.model_name, encoding_format="float") return [e.embedding for e in res.data] ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class OpenAIImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return OpenAIImage( model_name=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import List, Dict from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class OpenAIChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) streaming = model_kwargs.get('streaming', True) if 'o1' in model_name: streaming = False chat_open_ai = OpenAIChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params, streaming=streaming, custom_get_token_ids=custom_get_token_ids ) return chat_open_ai def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/stt.py ================================================ import asyncio import io from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class OpenAISpeechToText(MaxKBBaseModel, BaseSpeechToText): api_base: str api_key: str model: str params: dict @staticmethod def is_cache_model(): return False def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return OpenAISpeechToText( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), params = model_kwargs, **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def speech_to_text(self, audio_file): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) audio_data = audio_file.read() buffer = io.BytesIO(audio_data) buffer.name = "file.mp3" # this is the important line filter_params = {k: v for k,v in self.params.items() if k not in {'model_id','use_local','streaming'}} transcription_params = { 'model': self.model, 'file': buffer, 'language': 'zh' } res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params) return res.text ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/tti.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class OpenAITextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return OpenAITextToImage( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) response_list = chat.models.with_raw_response.list() # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) res = chat.images.generate(model=self.model, prompt=prompt, **self.params) file_urls = [] try: for content in res.data: if content.url: file_urls.append(content.url) elif content.b64_json: file_urls.append(content.b64_json) return file_urls except Exception as e: raise f"OpenAI generate image error: {e}" ================================================ FILE: apps/models_provider/impl/openai_model_provider/model/tts.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class OpenAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': 'alloy'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return OpenAITextToSpeech( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def text_to_speech(self, text): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) text = _remove_empty_lines(text) with client.audio.speech.with_streaming_response.create( model=self.model, input=text, **self.params ) as response: return response.read() ================================================ FILE: apps/models_provider/impl/openai_model_provider/openai_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: openai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.openai_model_provider.credential.embedding import OpenAIEmbeddingCredential from models_provider.impl.openai_model_provider.credential.image import OpenAIImageModelCredential from models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential from models_provider.impl.openai_model_provider.credential.stt import OpenAISTTModelCredential from models_provider.impl.openai_model_provider.credential.tti import OpenAITextToImageModelCredential from models_provider.impl.openai_model_provider.credential.tts import OpenAITTSModelCredential from models_provider.impl.openai_model_provider.model.embedding import OpenAIEmbeddingModel from models_provider.impl.openai_model_provider.model.image import OpenAIImage from models_provider.impl.openai_model_provider.model.llm import OpenAIChatModel from models_provider.impl.openai_model_provider.model.stt import OpenAISpeechToText from models_provider.impl.openai_model_provider.model.tti import OpenAITextToImage from models_provider.impl.openai_model_provider.model.tts import OpenAITextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext_lazy as _ openai_llm_model_credential = OpenAILLMModelCredential() openai_stt_model_credential = OpenAISTTModelCredential() openai_tts_model_credential = OpenAITTSModelCredential() openai_image_model_credential = OpenAIImageModelCredential() openai_tti_model_credential = OpenAITextToImageModelCredential() model_info_list = [ ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel ), ModelInfo('gpt-4', _('Latest gpt-4, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4o-mini', _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4-turbo', _('The latest gpt-4-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4-turbo-preview', _('The latest gpt-4-turbo-preview, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-3.5-turbo-0125', _('gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-3.5-turbo-1106', _('gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-3.5-turbo-0613', _('[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June 13, 2024'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4o-2024-05-13', _('gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4-turbo-2024-04-09', _('gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4-0125-preview', _('gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('gpt-4-1106-preview', _('gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 tokens'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel), ModelInfo('whisper-1', '', ModelTypeConst.STT, openai_stt_model_credential, OpenAISpeechToText), ModelInfo('tts-1', '', ModelTypeConst.TTS, openai_tts_model_credential, OpenAITextToSpeech) ] open_ai_embedding_credential = OpenAIEmbeddingCredential() model_info_embedding_list = [ ModelInfo('text-embedding-ada-002', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, OpenAIEmbeddingModel), ModelInfo('text-embedding-3-small', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, OpenAIEmbeddingModel), ModelInfo('text-embedding-3-large', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, OpenAIEmbeddingModel) ] model_info_image_list = [ ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI adjustments'), ModelTypeConst.IMAGE, openai_image_model_credential, OpenAIImage), ModelInfo('gpt-4o-mini', _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI adjustments'), ModelTypeConst.IMAGE, openai_image_model_credential, OpenAIImage), ] model_info_tti_list = [ ModelInfo('dall-e-3', '', ModelTypeConst.TTI, openai_tti_model_credential, OpenAITextToImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info(ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, OpenAIChatModel )) .append_model_info_list(model_info_embedding_list) .append_default_model_info(model_info_embedding_list[0]) .append_model_info_list(model_info_image_list) .append_default_model_info(model_info_image_list[0]) .append_model_info_list(model_info_tti_list) .append_default_model_info(model_info_tti_list[0]) .append_default_model_info(ModelInfo('whisper-1', '', ModelTypeConst.STT, openai_stt_model_credential, OpenAISpeechToText) ) .append_default_model_info(ModelInfo('tts-1', '', ModelTypeConst.TTS, openai_tts_model_credential, OpenAITextToSpeech)) .build() ) class OpenAIModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_openai_provider', name='OpenAI', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'openai_model_provider', 'icon', 'openai_icon_svg'))) ================================================ FILE: apps/models_provider/impl/qwen_model_provider/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/qwen_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/qwen_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/regolo_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/regolo_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class RegoloEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://api.regolo.ai/v1') api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/regolo_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from django.utils.translation import gettext_lazy as _, gettext from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class RegoloImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class RegoloImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1') api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return RegoloImageModelParams() ================================================ FILE: apps/models_provider/impl/regolo_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class RegoloLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class RegoloLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1') api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return RegoloLLMModelParams() ================================================ FILE: apps/models_provider/impl/regolo_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class RegoloTTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('The image generation endpoint allows you to create raw images based on text prompts. ')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), _(''' By default, images are produced in standard quality. ''')), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('1 as default')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class RegoloTextToImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1') api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return RegoloTTIModelParams() ================================================ FILE: apps/models_provider/impl/regolo_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict from langchain_openai import OpenAIEmbeddings from models_provider.base_model_provider import MaxKBBaseModel class RegoloEmbeddingModel(MaxKBBaseModel, OpenAIEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return RegoloEmbeddingModel( api_key=model_credential.get('api_key'), model=model_name, openai_api_base=model_credential.get('api_base') or "https://api.regolo.ai/v1", ) ================================================ FILE: apps/models_provider/impl/regolo_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class RegoloImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return RegoloImage( model_name=model_name, openai_api_base=model_credential.get('api_base') or "https://api.regolo.ai/v1", openai_api_key=model_credential.get('api_key'), streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/regolo_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import List, Dict from langchain_core.messages import BaseMessage, get_buffer_string from langchain_openai.chat_models import ChatOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class RegoloChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return RegoloChatModel( model=model_name, openai_api_base=model_credential.get('api_base') or "https://api.regolo.ai/v1", openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/regolo_model_provider/model/tti.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class RegoloTextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return RegoloTextToImage( model=model_name, api_base=model_credential.get('api_base') or "https://api.regolo.ai/v1", api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) response_list = chat.models.with_raw_response.list() # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) res = chat.images.generate(model=self.model, prompt=prompt, **self.params) file_urls = [] try: for content in res.data: url = content.url file_urls.append(url) return file_urls except Exception as e: raise f"RegoloTextToImage generate_image error: {e}" ================================================ FILE: apps/models_provider/impl/regolo_model_provider/regolo_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: openai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from django.utils.translation import gettext as _ from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR from models_provider.base_model_provider import ModelInfo, ModelTypeConst, ModelInfoManage, IModelProvider, \ ModelProvideInfo from models_provider.impl.regolo_model_provider.credential.embedding import RegoloEmbeddingCredential from models_provider.impl.regolo_model_provider.credential.llm import RegoloLLMModelCredential from models_provider.impl.regolo_model_provider.credential.tti import RegoloTextToImageModelCredential from models_provider.impl.regolo_model_provider.model.embedding import RegoloEmbeddingModel from models_provider.impl.regolo_model_provider.model.llm import RegoloChatModel from models_provider.impl.regolo_model_provider.model.tti import RegoloTextToImage openai_llm_model_credential = RegoloLLMModelCredential() openai_tti_model_credential = RegoloTextToImageModelCredential() model_info_list = [ ModelInfo('Phi-4', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel ), ModelInfo('DeepSeek-R1-Distill-Qwen-32B', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel), ModelInfo('maestrale-chat-v0.4-beta', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel), ModelInfo('Llama-3.3-70B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel), ModelInfo('Llama-3.1-8B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel), ModelInfo('DeepSeek-Coder-6.7B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel) ] open_ai_embedding_credential = RegoloEmbeddingCredential() model_info_embedding_list = [ ModelInfo('gte-Qwen2', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, RegoloEmbeddingModel), ] model_info_tti_list = [ ModelInfo('FLUX.1-dev', '', ModelTypeConst.TTI, openai_tti_model_credential, RegoloTextToImage), ModelInfo('sdxl-turbo', '', ModelTypeConst.TTI, openai_tti_model_credential, RegoloTextToImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info( ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, RegoloChatModel )) .append_model_info_list(model_info_embedding_list) .append_default_model_info(model_info_embedding_list[0]) .append_model_info_list(model_info_tti_list) .append_default_model_info(model_info_tti_list[0]) .build() ) class RegoloModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_regolo_provider', name='Regolo', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'regolo_model_provider', 'icon', 'regolo_icon_svg'))) ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class SiliconCloudEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _, gettext class SiliconCloudImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class SiliconCloudImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return SiliconCloudImageModelParams() ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class SiliconCloudLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class SiliconCloudLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return SiliconCloudLLMModelParams() ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: reranker.py @date:2024/9/9 17:51 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.siliconCloud_model_provider.model.reranker import SiliconCloudReranker from common.utils.logger import maxkb_logger class SiliconCloudRerankerCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): if not model_type == 'RERANKER': raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model: SiliconCloudReranker = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=_('Hello'))], _('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class SiliconCloudSTTModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential,**model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class SiliconCloudTTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), _(''' By default, images are produced in standard quality, but with DALL·E 3 you can set quality: "hd" to enhance detail. Square, standard quality images are generated fastest. ''')), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class SiliconCloudTextToImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return SiliconCloudTTIModelParams() ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class SiliconCloudTTSModelGeneralParams(BaseForm): # alloy, echo, fable, onyx, nova, shimmer voice = forms.SingleSelect( TooltipLabel('Voice', _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')), required=True, default_value='fnlp/MOSS-TTSD-v0.5:alex', text_field='label', value_field='value', option_list=[ {'label': 'alex', 'value': 'fnlp/MOSS-TTSD-v0.5:alex'}, {'label': 'anna', 'value': 'fnlp/MOSS-TTSD-v0.5:anna'}, {'label': 'bella', 'value': 'fnlp/MOSS-TTSD-v0.5:bella'}, {'label': 'charles', 'value': 'fnlp/MOSS-TTSD-v0.5:charles'}, {'label': 'benjamin', 'value': 'fnlp/MOSS-TTSD-v0.5:benjamin'}, {'label': 'claire', 'value': 'fnlp/MOSS-TTSD-v0.5:claire'}, {'label': 'david', 'value': 'fnlp/MOSS-TTSD-v0.5:david'}, {'label': 'diana', 'value': 'fnlp/MOSS-TTSD-v0.5:diana'}, ]) class SiliconCloudTTSModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return SiliconCloudTTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/16 16:34 @desc: """ from typing import Dict, List from common.utils.logger import maxkb_logger import requests from models_provider.base_model_provider import MaxKBBaseModel class SiliconCloudEmbeddingModel(MaxKBBaseModel): model_name: str openai_api_key: str base_url: str optional_params: dict def __init__(self, api_key, model_name: str, base_url, optional_params: dict): self.openai_api_key = api_key self.base_url = base_url self.model_name = model_name self.optional_params = optional_params def is_cache_model(self): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return SiliconCloudEmbeddingModel( api_key=model_credential.get('api_key'), model_name=model_name, optional_params=optional_params, base_url=model_credential.get('api_base'), ) def embed_query(self, text: str) -> list: payload = { "model": self.model_name, "input": text, **self.optional_params } headers = { "Authorization": f"Bearer {self.openai_api_key}", "Content-Type": "application/json" } response = requests.post(self.base_url + '/embeddings', json=payload, headers=headers) data = response.json() if isinstance(data, dict): if data['data'] is None or 'code' in data: raise ValueError(f"Embedding API returned no data: {data}") # 假设返回结构中有 'data[0].embedding' return data["data"][0]["embedding"] else: maxkb_logger.error(f"Unexpected response from Embedding API: {data}") def embed_documents(self, texts: list) -> list: return [self.embed_query(text) for text in texts] ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class SiliconCloudImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return SiliconCloudImage( model_name=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import List, Dict from langchain_core.messages import BaseMessage, get_buffer_string from langchain_openai.chat_models import ChatOpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class SiliconCloudChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return SiliconCloudChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: siliconcloud_reranker.py @date:2024/9/10 9:45 @desc: SiliconCloud 文档重排封装 """ from typing import Sequence, Optional, Any, Dict import requests from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from models_provider.base_model_provider import MaxKBBaseModel from django.utils.translation import gettext as _ class SiliconCloudReranker(MaxKBBaseModel, BaseDocumentCompressor): api_base: Optional[str] """SiliconCloud API URL""" model: Optional[str] """SiliconCloud 重排模型 ID""" api_key: Optional[str] """API Key""" top_n: Optional[int] = 3 # 取前 N 个最相关的结果 @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return SiliconCloudReranker( api_base=model_credential.get('api_base'), model=model_name, api_key=model_credential.get('api_key'), top_n=model_kwargs.get('top_n', 3) ) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if not documents: return [] # 预处理文本 texts = [doc.page_content for doc in documents] # 发送请求到 SiliconCloud API headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": self.model, "query": query, "documents": texts, "top_n": self.top_n, "return_documents": True, } response = requests.post(f"{self.api_base}/rerank", json=payload, headers=headers) if response.status_code != 200: raise RuntimeError(f"SiliconCloud API 请求失败: {response.text}") res = response.json() # 解析返回结果 return [ Document( page_content=item.get('document', {}).get('text', ''), metadata={'relevance_score': item.get('relevance_score')} ) for item in res.get('results', []) ] ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/stt.py ================================================ import asyncio import io from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class SiliconCloudSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.params = kwargs.get('params') @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return SiliconCloudSpeechToText( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), params=model_kwargs, **optional_params, ) @staticmethod def is_cache_model(): return False def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def speech_to_text(self, audio_file): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) audio_data = audio_file.read() buffer = io.BytesIO(audio_data) buffer.name = "file.mp3" # this is the important line filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}} transcription_params = { 'model': self.model, 'file': buffer, 'language': 'zh' } res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params) return res.text ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/tti.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class SiliconCloudTextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return SiliconCloudTextToImage( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) response_list = chat.models.with_raw_response.list() # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) res = chat.images.generate(model=self.model, prompt=prompt, **self.params) file_urls = [] try: for content in res.data: url = content.url file_urls.append(url) return file_urls except Exception as e: raise e ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/model/tts.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class SiliconCloudTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': 'alloy'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return SiliconCloudTextToSpeech( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def text_to_speech(self, text): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) text = _remove_empty_lines(text) with client.audio.speech.with_streaming_response.create( model=self.model, input=text, **self.params ) as response: return response.read() def is_cache_model(self): return False ================================================ FILE: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: openai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.siliconCloud_model_provider.credential.embedding import \ SiliconCloudEmbeddingCredential from models_provider.impl.siliconCloud_model_provider.credential.image import SiliconCloudImageModelCredential from models_provider.impl.siliconCloud_model_provider.credential.llm import SiliconCloudLLMModelCredential from models_provider.impl.siliconCloud_model_provider.credential.reranker import SiliconCloudRerankerCredential from models_provider.impl.siliconCloud_model_provider.credential.stt import SiliconCloudSTTModelCredential from models_provider.impl.siliconCloud_model_provider.credential.tti import \ SiliconCloudTextToImageModelCredential from models_provider.impl.siliconCloud_model_provider.credential.tts import SiliconCloudTTSModelCredential from models_provider.impl.siliconCloud_model_provider.model.embedding import SiliconCloudEmbeddingModel from models_provider.impl.siliconCloud_model_provider.model.image import SiliconCloudImage from models_provider.impl.siliconCloud_model_provider.model.llm import SiliconCloudChatModel from models_provider.impl.siliconCloud_model_provider.model.reranker import SiliconCloudReranker from models_provider.impl.siliconCloud_model_provider.model.stt import SiliconCloudSpeechToText from models_provider.impl.siliconCloud_model_provider.model.tti import SiliconCloudTextToImage from models_provider.impl.siliconCloud_model_provider.model.tts import SiliconCloudTextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ openai_llm_model_credential = SiliconCloudLLMModelCredential() openai_stt_model_credential = SiliconCloudSTTModelCredential() openai_reranker_model_credential = SiliconCloudRerankerCredential() openai_tti_model_credential = SiliconCloudTextToImageModelCredential() openai_image_model_credential = SiliconCloudImageModelCredential() openai_tts_model_credential = SiliconCloudTTSModelCredential() model_info_list = [ ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Llama-8B', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel ), ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Qwen-7B', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('Qwen/Qwen2.5-7B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('Qwen/Qwen2.5-Coder-7B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('internlm/internlm2_5-7b-chat', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('Qwen/Qwen2-1.5B-Instruct', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('THUDM/glm-4-9b-chat', '', ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel), ModelInfo('FunAudioLLM/SenseVoiceSmall', '', ModelTypeConst.STT, openai_stt_model_credential, SiliconCloudSpeechToText), ] open_ai_embedding_credential = SiliconCloudEmbeddingCredential() model_info_embedding_list = [ ModelInfo('netease-youdao/bce-embedding-base_v1', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, SiliconCloudEmbeddingModel), ModelInfo('BAAI/bge-m3', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, SiliconCloudEmbeddingModel), ModelInfo('BAAI/bge-large-en-v1.5', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, SiliconCloudEmbeddingModel), ModelInfo('BAAI/bge-large-zh-v1.5', '', ModelTypeConst.EMBEDDING, open_ai_embedding_credential, SiliconCloudEmbeddingModel), ] model_info_tti_list = [ ModelInfo('deepseek-ai/Janus-Pro-7B', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ModelInfo('stabilityai/stable-diffusion-3-5-large', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ModelInfo('black-forest-labs/FLUX.1-schnell', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ModelInfo('stabilityai/stable-diffusion-3-medium', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ModelInfo('stabilityai/stable-diffusion-xl-base-1.0', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ModelInfo('stabilityai/stable-diffusion-2-1', '', ModelTypeConst.TTI, openai_tti_model_credential, SiliconCloudTextToImage), ] model_rerank_list = [ ModelInfo('netease-youdao/bce-reranker-base_v1', '', ModelTypeConst.RERANKER, openai_reranker_model_credential, SiliconCloudReranker ), ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER, openai_reranker_model_credential, SiliconCloudReranker ), ] model_tts_list = [ ModelInfo('FunAudioLLM/CosyVoice2-0.5B', '', ModelTypeConst.TTS, openai_tts_model_credential, SiliconCloudTextToSpeech), ] model_image_info_list = [ ModelInfo('Qwen/Qwen3-VL-32B-Instruct', '', ModelTypeConst.IMAGE, openai_image_model_credential, SiliconCloudImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info( ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential, SiliconCloudChatModel )) .append_model_info_list(model_info_embedding_list) .append_default_model_info(model_info_embedding_list[0]) .append_model_info_list(model_info_tti_list) .append_default_model_info(model_info_tti_list[0]) .append_default_model_info(ModelInfo('whisper-1', '', ModelTypeConst.STT, openai_stt_model_credential, SiliconCloudSpeechToText)) .append_model_info_list(model_rerank_list) .append_default_model_info(model_rerank_list[0]) .append_model_info_list(model_tts_list) .append_default_model_info(model_tts_list[0]) .append_model_info_list(model_image_info_list) .append_default_model_info(model_image_info_list[0]) .build() ) class SiliconCloudModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_siliconCloud_provider', name='SILICONFLOW', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'siliconCloud_model_provider', 'icon', 'siliconCloud_icon_svg'))) ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/3/28 16:25 @desc: """ ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:32 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class TencentCloudLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class TencentCloudLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return TencentCloudLLMModelParams() ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/18 15:28 @desc: """ from typing import Dict from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class TencentCloudChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) azure_chat_open_ai = TencentCloudChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params, custom_get_token_ids=custom_get_token_ids ) return azure_chat_open_ai ================================================ FILE: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: openai_model_provider.py @date:2024/3/28 16:26 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \ ModelTypeConst, ModelInfoManage from models_provider.impl.openai_model_provider.credential.embedding import OpenAIEmbeddingCredential from models_provider.impl.openai_model_provider.credential.image import OpenAIImageModelCredential from models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential from models_provider.impl.openai_model_provider.credential.stt import OpenAISTTModelCredential from models_provider.impl.openai_model_provider.credential.tti import OpenAITextToImageModelCredential from models_provider.impl.openai_model_provider.credential.tts import OpenAITTSModelCredential from models_provider.impl.openai_model_provider.model.embedding import OpenAIEmbeddingModel from models_provider.impl.openai_model_provider.model.image import OpenAIImage from models_provider.impl.openai_model_provider.model.llm import OpenAIChatModel from models_provider.impl.openai_model_provider.model.stt import OpenAISpeechToText from models_provider.impl.openai_model_provider.model.tti import OpenAITextToImage from models_provider.impl.openai_model_provider.model.tts import OpenAITextToSpeech from models_provider.impl.tencent_cloud_model_provider.credential.llm import TencentCloudLLMModelCredential from models_provider.impl.tencent_cloud_model_provider.model.llm import TencentCloudChatModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext_lazy as _ openai_llm_model_credential = TencentCloudLLMModelCredential() model_info_list = [ ModelInfo('deepseek-v3', '', ModelTypeConst.LLM, openai_llm_model_credential, TencentCloudChatModel ), ModelInfo('deepseek-r1', '', ModelTypeConst.LLM, openai_llm_model_credential, TencentCloudChatModel ), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info( ModelInfo('deepseek-v3', '', ModelTypeConst.LLM, openai_llm_model_credential, TencentCloudChatModel )) .build() ) class TencentCloudModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_tencent_cloud_provider', name=_('Tencent Cloud'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'tencent_cloud_model_provider', 'icon', 'tencent_cloud_icon_svg'))) ================================================ FILE: apps/models_provider/impl/tencent_model_provider/__init__.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- ================================================ FILE: apps/models_provider/impl/tencent_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/tencent_model_provider/credential/embedding.py ================================================ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class TencentEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True) -> bool: model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) self.valid_form(model_credential) try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: encrypted_secret_key = super().encryption(model.get('SecretKey', '')) return {**model, 'SecretKey': encrypted_secret_key} SecretId = forms.PasswordInputField('SecretId', required=True) SecretKey = forms.PasswordInputField('SecretKey', required=True) ================================================ FILE: apps/models_provider/impl/tencent_model_provider/credential/image.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 18:41 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class TencentModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=1.0, _min=0.1, _max=1.9, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class TencentVisionModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True, default_value='https://api.hunyuan.cloud.tencent.com/v1') api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return TencentModelParams() ================================================ FILE: apps/models_provider/impl/tencent_model_provider/credential/llm.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class TencentLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.5, _min=0.1, _max=2.0, _step=0.01, precision=2) class TencentLLMModelCredential(BaseForm, BaseModelCredential): REQUIRED_FIELDS = ['hunyuan_app_id', 'hunyuan_secret_id', 'hunyuan_secret_key'] @classmethod def _validate_model_type(cls, model_type, provider, raise_exception=False): if not any(mt['value'] == model_type for mt in provider.get_model_type_list()): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) return False return True @classmethod def _validate_credential_fields(cls, model_credential, raise_exception=False): missing_keys = [key for key in cls.REQUIRED_FIELDS if key not in model_credential] if missing_keys: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{keys} is required').format(keys=", ".join(missing_keys))) return False return True def is_valid(self, model_type, model_name, model_credential, model_params, provider, raise_exception=False): if not (self._validate_model_type(model_type, provider, raise_exception) and self._validate_credential_fields(model_credential, raise_exception)): return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return False return True def encryption_dict(self, model): return {**model, 'hunyuan_secret_key': super().encryption(model.get('hunyuan_secret_key', ''))} hunyuan_app_id = forms.TextInputField('APP ID', required=True) hunyuan_secret_id = forms.PasswordInputField('SecretId', required=True) hunyuan_secret_key = forms.PasswordInputField('SecretKey', required=True) def get_model_params_setting_form(self, model_name): return TencentLLMModelParams() ================================================ FILE: apps/models_provider/impl/tencent_model_provider/credential/tti.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class TencentTTIModelParams(BaseForm): Style = forms.SingleSelect( TooltipLabel(_('painting style'), _('If not passed, the default value is 201 (Japanese anime style)')), required=True, default_value='201', option_list=[ {'value': '000', 'label': _('Not limited to style')}, {'value': '101', 'label': _('ink painting')}, {'value': '102', 'label': _('concept art')}, {'value': '103', 'label': _('Oil painting 1')}, {'value': '118', 'label': _('Oil Painting 2 (Van Gogh)')}, {'value': '104', 'label': _('watercolor painting')}, {'value': '105', 'label': _('pixel art')}, {'value': '106', 'label': _('impasto style')}, {'value': '107', 'label': _('illustration')}, {'value': '108', 'label': _('paper cut style')}, {'value': '109', 'label': _('Impressionism 1 (Monet)')}, {'value': '119', 'label': _('Impressionism 2')}, {'value': '110', 'label': '2.5D'}, {'value': '111', 'label': _('classical portraiture')}, {'value': '112', 'label': _('black and white sketch')}, {'value': '113', 'label': _('cyberpunk')}, {'value': '114', 'label': _('science fiction style')}, {'value': '115', 'label': _('dark style')}, {'value': '116', 'label': '3D'}, {'value': '117', 'label': _('vaporwave')}, {'value': '201', 'label': _('Japanese animation')}, {'value': '202', 'label': _('monster style')}, {'value': '203', 'label': _('Beautiful ancient style')}, {'value': '204', 'label': _('retro anime')}, {'value': '301', 'label': _('Game cartoon hand drawing')}, {'value': '401', 'label': _('Universal realistic style')}, ], value_field='value', text_field='label' ) Resolution = forms.SingleSelect( TooltipLabel(_('Generate image resolution'), _('If not transmitted, the default value is 768:768.')), required=True, default_value='768:768', option_list=[ {'value': '768:768', 'label': '768:768(1:1)'}, {'value': '768:1024', 'label': '768:1024(3:4)'}, {'value': '1024:768', 'label': '1024:768(4:3)'}, {'value': '1024:1024', 'label': '1024:1024(1:1)'}, {'value': '720:1280', 'label': '720:1280(9:16)'}, {'value': '1280:720', 'label': '1280:720(16:9)'}, {'value': '768:1280', 'label': '768:1280(3:5)'}, {'value': '1280:768', 'label': '1280:768(5:3)'}, {'value': '1080:1920', 'label': '1080:1920(9:16)'}, {'value': '1920:1080', 'label': '1920:1080(16:9)'}, ], value_field='value', text_field='label' ) class TencentTTIModelCredential(BaseForm, BaseModelCredential): REQUIRED_FIELDS = ['hunyuan_secret_id', 'hunyuan_secret_key'] @classmethod def _validate_model_type(cls, model_type, provider, raise_exception=False): if not any(mt['value'] == model_type for mt in provider.get_model_type_list()): if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) return False return True @classmethod def _validate_credential_fields(cls, model_credential, raise_exception=False): missing_keys = [key for key in cls.REQUIRED_FIELDS if key not in model_credential] if missing_keys: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{keys} is required').format(keys=", ".join(missing_keys))) return False return True def is_valid(self, model_type, model_name, model_credential, model_params, provider, raise_exception=False): if not (self._validate_model_type(model_type, provider, raise_exception) and self._validate_credential_fields(model_credential, raise_exception)): return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return False return True def encryption_dict(self, model): return {**model, 'hunyuan_secret_key': super().encryption(model.get('hunyuan_secret_key', ''))} hunyuan_secret_id = forms.PasswordInputField('SecretId', required=True) hunyuan_secret_key = forms.PasswordInputField('SecretKey', required=True) def get_model_params_setting_form(self, model_name): return TencentTTIModelParams() ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/embedding.py ================================================ from typing import Dict, List from langchain_core.embeddings import Embeddings from tencentcloud.common import credential from tencentcloud.hunyuan.v20230901.hunyuan_client import HunyuanClient from tencentcloud.hunyuan.v20230901.models import GetEmbeddingRequest from models_provider.base_model_provider import MaxKBBaseModel class TencentEmbeddingModel(MaxKBBaseModel, Embeddings): def embed_documents(self, texts: List[str]) -> List[List[float]]: return [self.embed_query(text) for text in texts] def embed_query(self, text: str) -> List[float]: request = GetEmbeddingRequest() request.Input = text res = self.client.GetEmbedding(request) return res.Data[0].Embedding def __init__(self, secret_id: str, secret_key: str, model_name: str): self.secret_id = secret_id self.secret_key = secret_key self.model_name = model_name cred = credential.Credential( secret_id, secret_key ) self.client = HunyuanClient(cred, "") @staticmethod def new_instance(model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs): return TencentEmbeddingModel( secret_id=model_credential.get('SecretId'), secret_key=model_credential.get('SecretKey'), model_name=model_name, ) def _generate_auth_token(self): # Example method to generate an authentication token for the model API return f"{self.secret_id}:{self.secret_key}" ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/hunyuan.py ================================================ import json import logging from typing import Any, Dict, Iterator, List, Mapping, Optional, Type from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models.chat_models import ( BaseChatModel, generate_from_stream, ) from langchain_core.messages import ( AIMessage, AIMessageChunk, BaseMessage, BaseMessageChunk, ChatMessage, ChatMessageChunk, HumanMessage, HumanMessageChunk, SystemMessage, ) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult from pydantic import Field, SecretStr, root_validator from langchain_core.utils import ( convert_to_secret_str, get_from_dict_or_env, get_pydantic_field_names, pre_init, ) logger = logging.getLogger(__name__) def _convert_message_to_dict(message: BaseMessage) -> dict: message_dict: Dict[str, Any] if isinstance(message, ChatMessage): message_dict = {"Role": message.role, "Content": message.content} elif isinstance(message, HumanMessage): message_dict = {"Role": "user", "Content": message.content} elif isinstance(message, AIMessage): message_dict = {"Role": "assistant", "Content": message.content} elif isinstance(message, SystemMessage): message_dict = {"Role": "system", "Content": message.content} else: raise TypeError(f"Got unknown type {message}") return message_dict def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage: role = _dict["Role"] if role == "user": return HumanMessage(content=_dict["Content"]) elif role == "assistant": return AIMessage(content=_dict.get("Content", "") or "") else: return ChatMessage(content=_dict["Content"], role=role) def _convert_delta_to_message_chunk( _dict: Mapping[str, Any], default_class: Type[BaseMessageChunk] ) -> BaseMessageChunk: role = _dict.get("Role") content = _dict.get("Content") or "" if role == "user" or default_class == HumanMessageChunk: return HumanMessageChunk(content=content) elif role == "assistant" or default_class == AIMessageChunk: return AIMessageChunk(content=content) elif role or default_class == ChatMessageChunk: return ChatMessageChunk(content=content, role=role) # type: ignore[arg-type] else: return default_class(content=content) # type: ignore[call-arg] def _create_chat_result(response: Mapping[str, Any]) -> ChatResult: generations = [] for choice in response["Choices"]: message = _convert_dict_to_message(choice["Message"]) generations.append(ChatGeneration(message=message)) token_usage = response["Usage"] llm_output = {"token_usage": token_usage} return ChatResult(generations=generations, llm_output=llm_output) class ChatHunyuan(BaseChatModel): """Tencent Hunyuan chat models API by Tencent. For more information, see https://cloud.tencent.com/document/product/1729 """ @property def lc_secrets(self) -> Dict[str, str]: return { "hunyuan_app_id": "HUNYUAN_APP_ID", "hunyuan_secret_id": "HUNYUAN_SECRET_ID", "hunyuan_secret_key": "HUNYUAN_SECRET_KEY", } @property def lc_serializable(self) -> bool: return True hunyuan_app_id: Optional[int] = None """Hunyuan App ID""" hunyuan_secret_id: Optional[str] = None """Hunyuan Secret ID""" hunyuan_secret_key: Optional[SecretStr] = None """Hunyuan Secret Key""" streaming: bool = False """Whether to stream the results or not.""" request_timeout: int = 60 """Timeout for requests to Hunyuan API. Default is 60 seconds.""" temperature: float = 1.0 """What sampling temperature to use.""" top_p: float = 1.0 """What probability mass to use.""" model: str = "hunyuan-lite" """What Model to use. Optional model: - hunyuan-lite、 - hunyuan-standard - hunyuan-standard-256K - hunyuan-pro - hunyuan-code - hunyuan-role - hunyuan-functioncall - hunyuan-vision """ stream_moderation: bool = False """Whether to review the results or not when streaming is true.""" enable_enhancement: bool = True """Whether to enhancement the results or not.""" model_kwargs: Dict[str, Any] = Field(default_factory=dict) """Holds any model parameters valid for API call not explicitly specified.""" class Config: """Configuration for this pydantic object.""" validate_by_name = True @root_validator(pre=True) def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) extra = values.get("model_kwargs", {}) for field_name in list(values): if field_name in extra: raise ValueError(f"Found {field_name} supplied twice.") if field_name not in all_required_field_names: logger.warning( f"""WARNING! {field_name} is not default parameter. {field_name} was transferred to model_kwargs. Please confirm that {field_name} is what you intended.""" ) extra[field_name] = values.pop(field_name) invalid_model_kwargs = all_required_field_names.intersection(extra.keys()) if invalid_model_kwargs: raise ValueError( f"Parameters {invalid_model_kwargs} should be specified explicitly. " f"Instead they were passed in as part of `model_kwargs` parameter." ) values["model_kwargs"] = extra return values @pre_init def validate_environment(cls, values: Dict) -> Dict: values["hunyuan_app_id"] = get_from_dict_or_env( values, "hunyuan_app_id", "HUNYUAN_APP_ID", ) values["hunyuan_secret_id"] = get_from_dict_or_env( values, "hunyuan_secret_id", "HUNYUAN_SECRET_ID", ) values["hunyuan_secret_key"] = convert_to_secret_str( get_from_dict_or_env( values, "hunyuan_secret_key", "HUNYUAN_SECRET_KEY", ) ) return values @property def _default_params(self) -> Dict[str, Any]: """Get the default parameters for calling Hunyuan API.""" normal_params = { "Temperature": self.temperature, "TopP": self.top_p, "Model": self.model, "Stream": self.streaming, "StreamModeration": self.stream_moderation, "EnableEnhancement": self.enable_enhancement, } return {**normal_params, **self.model_kwargs} def _generate( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> ChatResult: if self.streaming: stream_iter = self._stream( messages=messages, stop=stop, run_manager=run_manager, **kwargs ) return generate_from_stream(stream_iter) res = self._chat(messages, **kwargs) return _create_chat_result(json.loads(res.to_json_string())) usage_metadata: dict = {} def _stream( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: res = self._chat(messages, **kwargs) default_chunk_class = AIMessageChunk for chunk in res: chunk = chunk.get("data", "") if len(chunk) == 0: continue response = json.loads(chunk) if "error" in response: raise ValueError(f"Error from Hunyuan api response: {response}") for choice in response["Choices"]: chunk = _convert_delta_to_message_chunk( choice["Delta"], default_chunk_class ) default_chunk_class = chunk.__class__ # FinishReason === stop if choice.get("FinishReason") == "stop": self.usage_metadata = response.get("Usage", {}) cg_chunk = ChatGenerationChunk(message=chunk) if run_manager: run_manager.on_llm_new_token(chunk.content, chunk=cg_chunk) yield cg_chunk def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> Any: if self.hunyuan_secret_key is None: raise ValueError("Hunyuan secret key is not set.") try: from tencentcloud.common import credential from tencentcloud.hunyuan.v20230901 import hunyuan_client, models except ImportError: raise ImportError( "Could not import tencentcloud python package. " "Please install it with `pip install tencentcloud-sdk-python`." ) parameters = {**self._default_params, **kwargs} cred = credential.Credential( self.hunyuan_secret_id, str(self.hunyuan_secret_key.get_secret_value()) ) client = hunyuan_client.HunyuanClient(cred, "") req = models.ChatCompletionsRequest() params = { "Messages": [_convert_message_to_dict(m) for m in messages], **parameters, } req.from_json_string(json.dumps(params)) resp = client.ChatCompletions(req) return resp @property def _llm_type(self) -> str: return "hunyuan-chat" ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class TencentVision(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return TencentVision( model_name=model_name, openai_api_base=model_credential.get('api_base') or 'https://api.hunyuan.cloud.tencent.com/v1', openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) @staticmethod def is_cache_model(): return False ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/llm.py ================================================ # coding=utf-8 from typing import List, Dict, Optional, Any from langchain_core.messages import BaseMessage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.tencent_model_provider.model.hunyuan import ChatHunyuan class TencentModel(MaxKBBaseModel, ChatHunyuan): @staticmethod def is_cache_model(): return False def __init__(self, model_name: str, credentials: Dict[str, str], streaming: bool = False, **kwargs): hunyuan_app_id = credentials.get('hunyuan_app_id') hunyuan_secret_id = credentials.get('hunyuan_secret_id') hunyuan_secret_key = credentials.get('hunyuan_secret_key') optional_params = MaxKBBaseModel.filter_optional_params(kwargs) if not all([hunyuan_app_id, hunyuan_secret_id, hunyuan_secret_key]): raise ValueError( "All of 'hunyuan_app_id', 'hunyuan_secret_id', and 'hunyuan_secret_key' must be provided in credentials.") super().__init__(model=model_name, hunyuan_app_id=hunyuan_app_id, hunyuan_secret_id=hunyuan_secret_id, hunyuan_secret_key=hunyuan_secret_key, streaming=streaming, temperature=optional_params.get('temperature', 1.0) ) @staticmethod def new_instance(model_type: str, model_name: str, model_credential: Dict[str, object], **model_kwargs) -> 'TencentModel': streaming = model_kwargs.pop('streaming', False) return TencentModel(model_name=model_name, credentials=model_credential, streaming=streaming, **model_kwargs) def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.usage_metadata def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: return self.usage_metadata.get('PromptTokens', 0) def get_num_tokens(self, text: str) -> int: return self.usage_metadata.get('CompletionTokens', 0) ================================================ FILE: apps/models_provider/impl/tencent_model_provider/model/tti.py ================================================ # coding=utf-8 import json import logging from typing import Dict from django.utils.translation import gettext as _ from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.hunyuan.v20230901 import hunyuan_client, models from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage from models_provider.impl.tencent_model_provider.model.hunyuan import ChatHunyuan class TencentTextToImageModel(MaxKBBaseModel, BaseTextToImage): hunyuan_secret_id: str hunyuan_secret_key: str model: str params: dict @staticmethod def is_cache_model(): return False def __init__(self, **kwargs): super().__init__(**kwargs) self.hunyuan_secret_id = kwargs.get('hunyuan_secret_id') self.hunyuan_secret_key = kwargs.get('hunyuan_secret_key') self.model = kwargs.get('model_name') self.params = kwargs.get('params') @staticmethod def new_instance(model_type: str, model_name: str, model_credential: Dict[str, object], **model_kwargs) -> 'TencentTextToImageModel': optional_params = {'params': {'Style': '201', 'Resolution': '768:768'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return TencentTextToImageModel( model=model_name, hunyuan_secret_id=model_credential.get('hunyuan_secret_id'), hunyuan_secret_key=model_credential.get('hunyuan_secret_key'), **optional_params ) def check_auth(self): chat = ChatHunyuan(hunyuan_app_id='111111', hunyuan_secret_id=self.hunyuan_secret_id, hunyuan_secret_key=self.hunyuan_secret_key, model="hunyuan-standard") res = chat.invoke(_('Hello')) # print(res) def generate_image(self, prompt: str, negative_prompt: str = None): try: # 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密 # 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305 # 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取 cred = credential.Credential(self.hunyuan_secret_id, self.hunyuan_secret_key) # 实例化一个http选项,可选的,没有特殊需求可以跳过 httpProfile = HttpProfile() httpProfile.endpoint = "hunyuan.tencentcloudapi.com" # 实例化一个client选项,可选的,没有特殊需求可以跳过 clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile # 实例化要请求产品的client对象,clientProfile是可选的 client = hunyuan_client.HunyuanClient(cred, "ap-guangzhou", clientProfile) # 实例化一个请求对象,每个接口都会对应一个request对象 req = models.TextToImageLiteRequest() params = { "Prompt": prompt, "NegativePrompt": negative_prompt, "RspImgType": "url", **self.params } req.from_json_string(json.dumps(params)) # 返回的resp是一个TextToImageLiteResponse的实例,与请求对象对应 resp = client.TextToImageLite(req) file_urls = [] file_urls.append(resp.ResultImage) return file_urls except TencentCloudSDKException as err: maxkb_logger.error(f"Tencent Text to Image API call failed: {err}") raise f"Tencent Text to Image API call failed: {err}" ================================================ FILE: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- import os from common.utils.common import get_file_content from models_provider.base_model_provider import ( IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, ModelInfoManage ) from models_provider.impl.tencent_model_provider.credential.embedding import TencentEmbeddingCredential from models_provider.impl.tencent_model_provider.credential.image import TencentVisionModelCredential from models_provider.impl.tencent_model_provider.credential.llm import TencentLLMModelCredential from models_provider.impl.tencent_model_provider.credential.stt import TencentSTTModelCredential from models_provider.impl.tencent_model_provider.credential.tti import TencentTTIModelCredential from models_provider.impl.tencent_model_provider.model.embedding import TencentEmbeddingModel from models_provider.impl.tencent_model_provider.model.image import TencentVision from models_provider.impl.tencent_model_provider.model.llm import TencentModel from models_provider.impl.tencent_model_provider.model.stt import TencentSpeechToText from models_provider.impl.tencent_model_provider.model.tti import TencentTextToImageModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ def _create_model_info(model_name, description, model_type, credential_class, model_class): return ModelInfo( name=model_name, desc=description, model_type=model_type, model_credential=credential_class(), model_class=model_class ) def _get_tencent_icon_path(): return os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'tencent_model_provider', 'icon', 'tencent_icon_svg') def _initialize_model_info(): model_info_list = [_create_model_info( 'hunyuan-pro', _('The most effective version of the current hybrid model, the trillion-level parameter scale MOE-32K long article model. Reaching the absolute leading level on various benchmarks, with complex instructions and reasoning, complex mathematical capabilities, support for function call, and application focus optimization in fields such as multi-language translation, finance, law, and medical care'), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel ), _create_model_info( 'hunyuan-standard', _('A better routing strategy is adopted to simultaneously alleviate the problems of load balancing and expert convergence. For long articles, the needle-in-a-haystack index reaches 99.9%'), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel), _create_model_info( 'hunyuan-lite', _('Upgraded to MOE structure, the context window is 256k, leading many open source models in multiple evaluation sets such as NLP, code, mathematics, industry, etc.'), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel), _create_model_info( 'hunyuan-role', _("Hunyuan's latest version of the role-playing model, a role-playing model launched by Hunyuan's official fine-tuning training, is based on the Hunyuan model combined with the role-playing scene data set for additional training, and has better basic effects in role-playing scenes."), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel), _create_model_info( 'hunyuan-functioncall', _("Hunyuan's latest MOE architecture FunctionCall model has been trained with high-quality FunctionCall data and has a context window of 32K, leading in multiple dimensions of evaluation indicators."), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel), _create_model_info( 'hunyuan-code', _("Hunyuan's latest code generation model, after training the base model with 200B high-quality code data, and iterating on high-quality SFT data for half a year, the context long window length has been increased to 8K, and it ranks among the top in the automatic evaluation indicators of code generation in the five major languages; the five major languages In the manual high-quality evaluation of 10 comprehensive code tasks that consider all aspects, the performance is in the first echelon."), ModelTypeConst.LLM, TencentLLMModelCredential, TencentModel), _create_model_info( 'asr-sentence', _("This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects."), ModelTypeConst.STT, TencentSTTModelCredential, TencentSpeechToText), ] tencent_embedding_model_info = _create_model_info( 'hunyuan-embedding', _("Tencent's Hunyuan Embedding interface can convert text into high-quality vector data. The vector dimension is 1024 dimensions."), ModelTypeConst.EMBEDDING, TencentEmbeddingCredential, TencentEmbeddingModel ) model_info_embedding_list = [tencent_embedding_model_info] model_info_vision_list = [_create_model_info( 'hunyuan-vision', _('Mixed element visual model'), ModelTypeConst.IMAGE, TencentVisionModelCredential, TencentVision)] model_info_tti_list = [_create_model_info( 'hunyuan-dit', _('Hunyuan graph model'), ModelTypeConst.TTI, TencentTTIModelCredential, TencentTextToImageModel)] model_info_manage = ModelInfoManage.builder() \ .append_model_info_list(model_info_list) \ .append_model_info_list(model_info_embedding_list) \ .append_model_info_list(model_info_vision_list) \ .append_default_model_info(model_info_vision_list[0]) \ .append_model_info_list(model_info_tti_list) \ .append_default_model_info(model_info_tti_list[0]) \ .append_default_model_info(model_info_list[0]) \ .append_default_model_info(tencent_embedding_model_info) \ .build() return model_info_manage class TencentModelProvider(IModelProvider): def __init__(self): self._model_info_manage = _initialize_model_info() def get_model_info_manage(self): return self._model_info_manage def get_model_provide_info(self): icon_path = _get_tencent_icon_path() icon_data = get_file_content(icon_path) return ModelProvideInfo( provider='model_tencent_provider', name=_('Tencent Hunyuan'), icon=icon_data ) ================================================ FILE: apps/models_provider/impl/vllm_model_provider/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VllmEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class VllmImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class VllmImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return VllmImageModelParams() ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/llm.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class VLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid')) exist = provider.get_model_info_by_name(model_list, model_name) if len(exist) == 0: raise AppApiException(ValidCode.valid_error.value, gettext('The model does not exist, please download the model first')) model = provider.get_model(model_type, model_name, model_credential, **model_params) try: res = model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) return True def encryption_dict(self, model_info: Dict[str, object]): return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} def build_model(self, model_info: Dict[str, object]): for key in ['api_key', 'model']: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_key = model_info.get('api_key') return self api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return VLLMModelParams() ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/reranker.py ================================================ from typing import Dict from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from django.utils.translation import gettext_lazy as _ from models_provider.impl.vllm_model_provider.model.reranker import VllmBgeReranker from common.utils.logger import maxkb_logger class VllmRerankerCredential(BaseForm, BaseModelCredential): api_url = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_url', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model: VllmBgeReranker = provider.get_model(model_type, model_name, model_credential) test_text = str(_('Hello')) model.compress_documents([Document(page_content=test_text)], test_text) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException( ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e)) ) return False return True def encryption_dict(self, model_info: Dict[str, object]): return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} ================================================ FILE: apps/models_provider/impl/vllm_model_provider/credential/whisper_stt.py ================================================ # coding=utf-8 import traceback from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class VLLMWhisperModelParams(BaseForm): Language = forms.TextInputField( TooltipLabel(_('language'), _("If not passed, the default value is 'zh'")), required=True, default_value='zh', ) class VLLMWhisperModelCredential(BaseForm, BaseModelCredential): api_url = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_url'), model_credential.get('api_key')) except Exception as e: raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid')) exist = provider.get_model_info_by_name(model_list, model_name) if len(exist) == 0: raise AppApiException(ValidCode.valid_error.value, gettext('The model does not exist, please download the model first')) model = provider.get_model(model_type, model_name, model_credential, **model_params) return True def encryption_dict(self, model_info: Dict[str, object]): return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} def build_model(self, model_info: Dict[str, object]): for key in ['api_key', 'model']: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_key = model_info.get('api_key') return self def get_model_params_setting_form(self, model_name): return VLLMWhisperModelParams() ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 17:44 @desc: """ from typing import Dict from langchain_openai import OpenAIEmbeddings from models_provider.base_model_provider import MaxKBBaseModel class VllmEmbeddingModel(MaxKBBaseModel, OpenAIEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return VllmEmbeddingModel( model=model_name, openai_api_key=model_credential.get('api_key'), openai_api_base=model_credential.get('api_base'), ) ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/image.py ================================================ from typing import Dict, List from langchain_core.messages import get_buffer_string, BaseMessage from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class VllmImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return VllmImage( model_name=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=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) ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/llm.py ================================================ # coding=utf-8 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 models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class VllmChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) vllm_chat_open_ai = VllmChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params, streaming=True, 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) ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/reranker.py ================================================ from typing import Sequence, Optional, Dict, Any import cohere from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from models_provider.base_model_provider import MaxKBBaseModel class VllmBgeReranker(MaxKBBaseModel, BaseDocumentCompressor): api_key: str api_url: str model: str params: dict client: Any = None def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') self.api_url = kwargs.get('api_url') self.client = cohere.ClientV2(kwargs.get('api_key'), base_url=kwargs.get('api_url')) @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): r_url = model_credential.get('api_url')[:-3] if model_credential.get('api_url').endswith('/v1') else model_credential.get('api_url') return VllmBgeReranker( model=model_name, api_key=model_credential.get('api_key'), api_url=r_url, params=model_kwargs, **model_kwargs ) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if documents is None or len(documents) == 0: return [] ds = [d.page_content for d in documents] result = self.client.rerank(model=self.model, query=query, documents=ds) return [Document(page_content=d.document.get('text'), metadata={'relevance_score': d.relevance_score}) for d in result.results] ================================================ FILE: apps/models_provider/impl/vllm_model_provider/model/whisper_sst.py ================================================ import base64 import os import traceback from typing import Dict from openai import OpenAI from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText class VllmWhisperSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_key: str api_url: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') self.api_url = kwargs.get('api_url') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return VllmWhisperSpeechToText( model=model_name, api_key=model_credential.get('api_key'), api_url=model_credential.get('api_url'), params=model_kwargs, **model_kwargs ) def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file: self.speech_to_text(audio_file) def speech_to_text(self, audio_file): base_url = self.api_url if self.api_url.endswith('v1') else f"{self.api_url}/v1" try: client = OpenAI( api_key=self.api_key, base_url=base_url ) buf = audio_file.read() filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}} transcription_params = { 'model': self.model, 'file': buf, 'language': 'zh', } result = client.audio.transcriptions.create( **transcription_params, extra_body=filter_params ) return result.text except Exception as err: maxkb_logger.error(f":Error: {str(err)}: {traceback.format_exc()}") raise err ================================================ FILE: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py ================================================ # coding=utf-8 import os from urllib.parse import urlparse, ParseResult import requests from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ ModelInfoManage from models_provider.impl.vllm_model_provider.credential.embedding import VllmEmbeddingCredential from models_provider.impl.vllm_model_provider.credential.image import VllmImageModelCredential from models_provider.impl.vllm_model_provider.credential.llm import VLLMModelCredential from models_provider.impl.vllm_model_provider.credential.reranker import VllmRerankerCredential from models_provider.impl.vllm_model_provider.credential.whisper_stt import VLLMWhisperModelCredential from models_provider.impl.vllm_model_provider.model.embedding import VllmEmbeddingModel from models_provider.impl.vllm_model_provider.model.image import VllmImage from models_provider.impl.vllm_model_provider.model.llm import VllmChatModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ from models_provider.impl.vllm_model_provider.model.reranker import VllmBgeReranker from models_provider.impl.vllm_model_provider.model.whisper_sst import VllmWhisperSpeechToText v_llm_model_credential = VLLMModelCredential() image_model_credential = VllmImageModelCredential() embedding_model_credential = VllmEmbeddingCredential() whisper_model_credential = VLLMWhisperModelCredential() rerank_model_credential = VllmRerankerCredential() model_info_list = [ ModelInfo('facebook/opt-125m', _('Facebook’s 125M parameter model'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel), ModelInfo('BAAI/Aquila-7B', _('BAAI’s 7B parameter model'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel), ModelInfo('BAAI/AquilaChat-7B', _('BAAI’s 13B parameter mode'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel), ] image_model_info_list = [ ModelInfo('Qwen/Qwen2-VL-2B-Instruct', '', ModelTypeConst.IMAGE, image_model_credential, VllmImage), ] embedding_model_info_list = [ ModelInfo('HIT-TMG/KaLM-embedding-multilingual-mini-instruct-v1.5', '', ModelTypeConst.EMBEDDING, embedding_model_credential, VllmEmbeddingModel), ] whisper_model_info_list = [ ModelInfo('whisper-tiny', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText), ModelInfo('whisper-large-v3-turbo', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText), ModelInfo('whisper-small', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText), ModelInfo('whisper-large-v3', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText), ] reranker_model_info_list = [ ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER, rerank_model_credential, VllmBgeReranker), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info(ModelInfo('facebook/opt-125m', _('Facebook’s 125M parameter model'), ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel)) .append_model_info_list(image_model_info_list) .append_default_model_info(image_model_info_list[0]) .append_model_info_list(embedding_model_info_list) .append_default_model_info(embedding_model_info_list[0]) .append_model_info_list(whisper_model_info_list) .append_default_model_info(whisper_model_info_list[0]) .append_model_info_list(reranker_model_info_list) .append_default_model_info(reranker_model_info_list[0]) .build() ) def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class VllmModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_vllm_provider', name='vLLM', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'vllm_model_provider', 'icon', 'vllm_icon_svg'))) @staticmethod def get_base_model_list(api_base, api_key): base_url = get_base_url(api_base) base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1') headers = {} if api_key: headers['Authorization'] = f"Bearer {api_key}" r = requests.request(method="GET", url=f"{base_url}/models", headers=headers, timeout=5) r.raise_for_status() return r.json().get('data') @staticmethod def get_model_info_by_name(model_list, model_name): if model_list is None: return [] return [model for model in model_list if model.get('id') == model_name] ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/__init__.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/bigModel_stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineBigModelSTTModelParams(BaseForm): uid = forms.TextInputField( TooltipLabel(_('User ID'), _('If not passed, the default value is streaming_asr_demo')), required=True, default_value='streaming_asr_demo' ) class VolcanicEngineBigModelSTTModelCredential(BaseForm, BaseModelCredential): volcanic_app_id = forms.TextInputField('App ID', required=True) volcanic_token = forms.PasswordInputField('Access Token', required=True) volcanic_api_url = forms.TextInputField('API URL', required=True, default_value='https://openspeech.bytedance.com/api/v3/auc/bigmodel/recognize/flash') def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineBigModelSTTModelParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/7/12 16:45 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class VolcanicEngineImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.95, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class VolcanicEngineImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineImageModelParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/11 17:57 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.3, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class VolcanicEngineLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['access_key_id', 'secret_access_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'access_key_id': super().encryption(model.get('access_key_id', ''))} access_key_id = forms.PasswordInputField('Access Key ID', required=True) secret_access_key = forms.PasswordInputField('Secret Access Key', required=True) def get_model_params_setting_form(self, model_name): return VolcanicEngineLLMModelParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineSTTModelParams(BaseForm): uid = forms.TextInputField( TooltipLabel(_('User ID'),_('If not passed, the default value is streaming_asr_demo')), required=True, default_value='streaming_asr_demo' ) class VolcanicEngineSTTModelCredential(BaseForm, BaseModelCredential): volcanic_api_url = forms.TextInputField('API URL', required=True, default_value='wss://openspeech.bytedance.com/api/v2/asr') volcanic_app_id = forms.TextInputField('App ID', required=True) volcanic_token = forms.PasswordInputField('Access Token', required=True) volcanic_cluster = forms.TextInputField('Cluster ID', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token', 'volcanic_cluster']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineSTTModelParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineTTIModelGeneralParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('If the gap between width, height and 512 is too large, the picture rendering effect will be poor and the probability of excessive delay will increase significantly. Recommended ratio and corresponding width and height before super score: width*height')), required=True, default_value='512x512', option_list=[ {'label': '512x512', 'value': '512x512'}, {'label': '1024x1024', 'value': '1024x1024'}, {'label': '864x1152', 'value': '864x1152'}, {'label': '1152x864', 'value': '1152x864'}, {'label': '1280x720', 'value': '1280x720'}, {'label': '720x1280', 'value': '720x1280'}, {'label': '832x1248', 'value': '832x1248'}, {'label': '1248x832', 'value': '1248x832'}, {'label': '1512x648', 'value': '1512x648'}, ], text_field='label', value_field='value') class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential): volcanic_api_url = forms.TextInputField('API URL', required=True, default_value='https://ark.cn-beijing.volces.com/api/v3') api_key = forms.PasswordInputField('Api key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineTTIModelGeneralParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineTTSModelGeneralParams(BaseForm): voice_type = forms.SingleSelect( TooltipLabel(_('timbre'), _('Chinese sounds can support mixed scenes of Chinese and English')), required=True, default_value='zh_female_cancan_mars_bigtts', text_field='value', value_field='value', option_list=[ {'text': '灿灿/Shiny', 'value': 'zh_female_cancan_mars_bigtts'}, {'text': '清新女声', 'value': 'zh_female_qingxinnvsheng_mars_bigtts'}, {'text': '爽快思思/Skye', 'value': 'zh_female_shuangkuaisisi_moon_bigtts'}, {'text': '湾区大叔', 'value': 'zh_female_wanqudashu_moon_bigtts' }, {'text': '呆萌川妹', 'value': 'zh_female_daimengchuanmei_moon_bigtts'}, {'text': '广州德哥', 'value': 'zh_male_guozhoudege_moon_bigtts'}, {'text': '北京小爷', 'value': 'zh_male_beijingxiaoye_moon_bigtts'}, {'text': '少年梓辛/Brayan', 'value': 'zh_male_shaonianzixin_moon_bigtts'}, {'text': '魅力女友', 'value': 'zh_female_meilinvyou_moon_bigtts'}, ]) speed_ratio = forms.SliderField( TooltipLabel(_('speaking speed'), _('[0.2,3], the default is 1, usually one decimal place is enough')), required=True, default_value=1, _min=0.2, _max=3, _step=0.1, precision=1) class VolcanicEngineTTSModelCredential(BaseForm, BaseModelCredential): volcanic_api_url = forms.TextInputField('API URL', required=True, default_value='wss://openspeech.bytedance.com/api/v1/tts/ws_binary') volcanic_app_id = forms.TextInputField('App ID', required=True) volcanic_token = forms.PasswordInputField('Access Token', required=True) volcanic_cluster = forms.TextInputField('Cluster ID', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token', 'volcanic_cluster']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineTTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/credential/ttv.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel, SingleSelect, TextInputField from common.forms.switch_field import SwitchField from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class VolcanicEngineTTVModelGeneralParams(BaseForm): resolution = SingleSelect( TooltipLabel(_('Resolution'), _('Resolution')), required=True, default_value='480P', option_list=[ {'value': '480P', 'label': '480P'}, {'value': '720P', 'label': '720P'}, {'value': '1080P', 'label': '1080P'}, ], text_field='label', value_field='value' ) ratio = SingleSelect( TooltipLabel(_('Ratio'), _('Ratio')), required=True, default_value='16:9', option_list=[ {'value': '16:9', 'label': '16:9'}, {'value': '9:16', 'label': '9:16'}, {'value': '1:1', 'label': '1:1'}, {'value': '4:3', 'label': '4:3'}, {'value': '3:4', 'label': '3:4'}, {'value': '21:9', 'label': '21:9'}, ], text_field='label', value_field='value' ) duration = TextInputField( TooltipLabel(_('Duration'), _('Duration')), required=True, default_value=5, ) watermark = SwitchField( TooltipLabel(_('Watermark'), _('Whether to add watermark')), attrs={"active-value": "true", "inactive-value": "false"}, default_value=False, ) class VolcanicEngineTTVModelCredential(BaseForm, BaseModelCredential): api_key = forms.PasswordInputField('Api key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return VolcanicEngineTTVModelGeneralParams() ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/bigModel_stt.py ================================================ # coding=utf-8 """ requires Python 3.6 or later pip install asyncio pip install websockets """ import base64 import json import os import time import uuid import requests import uuid_utils.compat as uuid from typing import Dict from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText audio_format = "mp3" # wav 或者 mp3,根据实际音频格式设置 def determine_api_mode(url): """ 根据URL判断API模式 """ if '/recognize/flash' in url: return 'sync' elif '/submit' in url: return 'async_submit' elif '/query' in url: return 'async_query' else: return 'unknown' class VolcanicASRClient: def __init__(self, appid, token): self.appid = appid self.token = token def _build_headers(self, url, task_id=None, x_tt_logid=None): """根据URL构建请求头""" mode = determine_api_mode(url) headers = { "X-Api-App-Key": self.appid, "X-Api-Access-Key": self.token, } if mode == 'sync': headers.update({ "X-Api-Resource-Id": "volc.bigasr.auc_turbo", "X-Api-Request-Id": str(uuid.uuid4()), "X-Api-Sequence": "-1", }) elif mode == 'async_submit': headers.update({ "X-Api-Resource-Id": "volc.bigasr.auc", "X-Api-Request-Id": task_id or str(uuid.uuid4()), "X-Api-Sequence": "-1", }) elif mode == 'async_query': headers.update({ "X-Api-Resource-Id": "volc.bigasr.auc", "X-Api-Request-Id": task_id or str(uuid.uuid4()), "X-Tt-Logid": x_tt_logid or "", }) return headers def _create_request_body(self, audio_data, mode='sync'): """创建请求体""" base_request = { "user": {"uid": self.appid if mode == 'sync' else "fake_uid"}, "audio": audio_data, } if mode == 'sync': base_request["request"] = { "model_name": "bigmodel", "enable_itn": True, "enable_punc": True, "enable_ddc": True, } else: # async base_request["request"] = { "model_name": "bigmodel", "enable_channel_split": True, "enable_ddc": True, "enable_speaker_info": True, "enable_punc": True, "enable_itn": True, "corpus": { "correct_table_name": "", "context": "" } } return base_request def process_audio(self, audio_file=None, submit_url=None): """ 根据submit_url自动选择处理模式 """ # 获取音频数据 base64_audio = base64.b64encode(audio_file.read()).decode("utf-8") audio_data = {"data": base64_audio} # 根据URL判断API模式 mode = determine_api_mode(submit_url) if mode == 'sync': return self._sync_recognize(audio_data, submit_url) elif mode == 'async_submit': return self._async_process(audio_data, submit_url) else: raise ValueError(f"Unsupported URL pattern: {submit_url}") def _get_audio_data(self, audio_file): """构建音频数据对象""" base64_audio = base64.b64encode(audio_file.read()).decode("utf-8") return {"data": base64_audio} def _sync_recognize(self, audio_data, submit_url): """同步识别模式""" headers = self._build_headers(submit_url) request_body = self._create_request_body(audio_data, mode='sync') response = requests.post(submit_url, json=request_body, headers=headers) return self._handle_response(response, "sync_recognize") def _async_process(self, audio_data, submit_url): """异步处理模式""" # 提交任务 task_id = str(uuid.uuid4()) headers = self._build_headers(submit_url, task_id=task_id) request_body = self._create_request_body(audio_data, mode='async') submit_response = requests.post(submit_url, data=json.dumps(request_body), headers=headers) if submit_response.headers.get("X-Api-Status-Code") == "20000000": x_tt_logid = submit_response.headers.get("X-Tt-Logid", "") # 查询结果 return self._poll_for_result(task_id, x_tt_logid) else: print(f"Submit task failed: {submit_response.headers}") return None def _poll_for_result(self, task_id, x_tt_logid): """轮询查询异步任务结果""" query_url = "https://openspeech-direct.zijieapi.com/api/v3/auc/bigmodel/query" while True: query_response = self._query_task(task_id, x_tt_logid, query_url) code = query_response.headers.get('X-Api-Status-Code', "") if code == '20000000': # 任务完成 return query_response elif code != '20000001' and code != '20000002': # 任务失败 print(f"Async task failed with code: {code}") return None time.sleep(1) def _query_task(self, task_id, x_tt_logid, query_url): """执行单次查询请求""" headers = self._build_headers(query_url, task_id=task_id, x_tt_logid=x_tt_logid) response = requests.post(query_url, json.dumps({}), headers=headers) return self._handle_response(response, "async_query", silent=True) def _handle_response(self, response, operation, silent=False): """处理响应""" if 'X-Api-Status-Code' in response.headers: if not silent: print(f'{operation} response header X-Api-Status-Code: {response.headers["X-Api-Status-Code"]}') print(f'{operation} response header X-Api-Message: {response.headers["X-Api-Message"]}') print(f'{operation} response header X-Tt-Logid: {response.headers["X-Tt-Logid"]}') if operation == "sync_recognize": print(f'sync response content: {response.json()}\n') return response else: print(f'{operation} failed: {response.headers}\n') return None class VolcanicEngineBigModelSpeechToText(MaxKBBaseModel, BaseSpeechToText): volcanic_app_id: str volcanic_api_url: str volcanic_token: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.volcanic_api_url = kwargs.get('volcanic_api_url') self.volcanic_token = kwargs.get('volcanic_token') self.volcanic_app_id = kwargs.get('volcanic_app_id') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return VolcanicEngineBigModelSpeechToText( volcanic_api_url=model_credential.get('volcanic_api_url'), volcanic_token=model_credential.get('volcanic_token'), volcanic_app_id=model_credential.get('volcanic_app_id'), params=model_kwargs, ) def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file: self.speech_to_text(audio_file) def speech_to_text(self, audio_file): try: client = VolcanicASRClient(self.volcanic_app_id, self.volcanic_token) result = client.process_audio(audio_file, self.volcanic_api_url) if result.status_code == 200: return result.json().get('result').get('text') except Exception as e: maxkb_logger.error(f'Error getting speech to text: {e}') ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/embedding.py ================================================ from typing import Dict, List from models_provider.base_model_provider import MaxKBBaseModel from volcenginesdkarkruntime import Ark class VolcanicEngineEmbeddingModel(MaxKBBaseModel): api_key: str model_name: str api_base: str params: Dict[str, object] def __init__(self, api_key: str, model: str, api_base: str, params: Dict[str, object] = None): self.client = Ark( api_key=api_key, base_url=api_base ) self.model_name = model self.params = params @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return VolcanicEngineEmbeddingModel( api_key=model_credential.get("api_key"), model=model_name, api_base=model_credential.get("api_base"), **optional_params ) def embed_query(self, text: str): res = self.embed_documents([text]) return res[0] def embed_documents( self, texts: List[str], chunk_size: int | None = None ) -> List[List[float]]: if self.model_name.startswith("doubao-embedding-vision-"): multimodal_inputs = [] for text in texts: multimodal_inputs.append({ "type": "text", "text": text }) resp = self.client.multimodal_embeddings.create( model=self.model_name, input=multimodal_inputs, **(self.params or {}) ) return [resp.data.get('embedding')] else: resp = self.client.embeddings.create( model=self.model_name, input=texts, **(self.params or {}) ) return [e.embedding for e in resp.data] ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/image.py ================================================ import base64 import mimetypes from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class VolcanicEngineImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return VolcanicEngineImage( model_name=model_name, openai_api_key=model_credential.get('api_key'), openai_api_base=model_credential.get('api_base'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) @staticmethod def is_cache_model(): return False ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/llm.py ================================================ from typing import List, Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class VolcanicEngineChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return VolcanicEngineChatModel( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/stt.py ================================================ # coding=utf-8 """ requires Python 3.6 or later pip install asyncio pip install websockets """ import asyncio import base64 import gzip import hmac import json import logging import os import ssl import uuid_utils.compat as uuid import wave from hashlib import sha256 from io import BytesIO from typing import Dict from urllib.parse import urlparse import websockets from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText audio_format = "mp3" # wav 或者 mp3,根据实际音频格式设置 PROTOCOL_VERSION = 0b0001 DEFAULT_HEADER_SIZE = 0b0001 PROTOCOL_VERSION_BITS = 4 HEADER_BITS = 4 MESSAGE_TYPE_BITS = 4 MESSAGE_TYPE_SPECIFIC_FLAGS_BITS = 4 MESSAGE_SERIALIZATION_BITS = 4 MESSAGE_COMPRESSION_BITS = 4 RESERVED_BITS = 8 # Message Type: CLIENT_FULL_REQUEST = 0b0001 CLIENT_AUDIO_ONLY_REQUEST = 0b0010 SERVER_FULL_RESPONSE = 0b1001 SERVER_ACK = 0b1011 SERVER_ERROR_RESPONSE = 0b1111 # Message Type Specific Flags NO_SEQUENCE = 0b0000 # no check sequence POS_SEQUENCE = 0b0001 NEG_SEQUENCE = 0b0010 NEG_SEQUENCE_1 = 0b0011 # Message Serialization NO_SERIALIZATION = 0b0000 JSON = 0b0001 THRIFT = 0b0011 CUSTOM_TYPE = 0b1111 # Message Compression NO_COMPRESSION = 0b0000 GZIP = 0b0001 CUSTOM_COMPRESSION = 0b1111 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE def generate_header( version=PROTOCOL_VERSION, message_type=CLIENT_FULL_REQUEST, message_type_specific_flags=NO_SEQUENCE, serial_method=JSON, compression_type=GZIP, reserved_data=0x00, extension_header=bytes() ): """ protocol_version(4 bits), header_size(4 bits), message_type(4 bits), message_type_specific_flags(4 bits) serialization_method(4 bits) message_compression(4 bits) reserved (8bits) 保留字段 header_extensions 扩展头(大小等于 8 * 4 * (header_size - 1) ) """ header = bytearray() header_size = int(len(extension_header) / 4) + 1 header.append((version << 4) | header_size) header.append((message_type << 4) | message_type_specific_flags) header.append((serial_method << 4) | compression_type) header.append(reserved_data) header.extend(extension_header) return header def generate_full_default_header(): return generate_header() def generate_audio_default_header(): return generate_header( message_type=CLIENT_AUDIO_ONLY_REQUEST ) def generate_last_audio_default_header(): return generate_header( message_type=CLIENT_AUDIO_ONLY_REQUEST, message_type_specific_flags=NEG_SEQUENCE ) def parse_response(res): """ protocol_version(4 bits), header_size(4 bits), message_type(4 bits), message_type_specific_flags(4 bits) serialization_method(4 bits) message_compression(4 bits) reserved (8bits) 保留字段 header_extensions 扩展头(大小等于 8 * 4 * (header_size - 1) ) payload 类似与http 请求体 """ protocol_version = res[0] >> 4 header_size = res[0] & 0x0f message_type = res[1] >> 4 message_type_specific_flags = res[1] & 0x0f serialization_method = res[2] >> 4 message_compression = res[2] & 0x0f reserved = res[3] header_extensions = res[4:header_size * 4] payload = res[header_size * 4:] result = {} payload_msg = None payload_size = 0 if message_type == SERVER_FULL_RESPONSE: payload_size = int.from_bytes(payload[:4], "big", signed=True) payload_msg = payload[4:] elif message_type == SERVER_ACK: seq = int.from_bytes(payload[:4], "big", signed=True) result['seq'] = seq if len(payload) >= 8: payload_size = int.from_bytes(payload[4:8], "big", signed=False) payload_msg = payload[8:] elif message_type == SERVER_ERROR_RESPONSE: code = int.from_bytes(payload[:4], "big", signed=False) result['code'] = code payload_size = int.from_bytes(payload[4:8], "big", signed=False) payload_msg = payload[8:] maxkb_logger.error(f"Error code: {code}, message: {payload_msg}") if payload_msg is None: return result if message_compression == GZIP: payload_msg = gzip.decompress(payload_msg) if serialization_method == JSON: payload_msg = json.loads(str(payload_msg, "utf-8")) elif serialization_method != NO_SERIALIZATION: payload_msg = str(payload_msg, "utf-8") result['payload_msg'] = payload_msg result['payload_size'] = payload_size return result def read_wav_info(data: bytes = None) -> (int, int, int, int, int): with BytesIO(data) as _f: wave_fp = wave.open(_f, 'rb') nchannels, sampwidth, framerate, nframes = wave_fp.getparams()[:4] wave_bytes = wave_fp.readframes(nframes) return nchannels, sampwidth, framerate, nframes, len(wave_bytes) class VolcanicEngineSpeechToText(MaxKBBaseModel, BaseSpeechToText): workflow: str = "audio_in,resample,partition,vad,fe,decode,itn,nlu_punctuate" show_language: bool = False show_utterances: bool = False result_type: str = "full" format: str = "mp3" rate: int = 16000 language: str = "zh-CN" bits: int = 16 channel: int = 1 codec: str = "raw" audio_type: int = 1 secret: str = "access_secret" auth_method: str = "token" mp3_seg_size: int = 10000 success_code: int = 1000 # success code, default is 1000 seg_duration: int = 15000 nbest: int = 1 volcanic_app_id: str volcanic_cluster: str volcanic_api_url: str volcanic_token: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.volcanic_api_url = kwargs.get('volcanic_api_url') self.volcanic_token = kwargs.get('volcanic_token') self.volcanic_app_id = kwargs.get('volcanic_app_id') self.volcanic_cluster = kwargs.get('volcanic_cluster') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return VolcanicEngineSpeechToText( volcanic_api_url=model_credential.get('volcanic_api_url'), volcanic_token=model_credential.get('volcanic_token'), volcanic_app_id=model_credential.get('volcanic_app_id'), volcanic_cluster=model_credential.get('volcanic_cluster'), params=model_kwargs, **model_kwargs, **optional_params ) def construct_request(self, reqid): params = self.params or {} req = { 'app': { 'appid': self.volcanic_app_id, 'cluster': self.volcanic_cluster, 'token': self.volcanic_token, }, 'user': { 'uid': params.get("uid", "streaming_asr_demo") }, 'request': { 'reqid': reqid, 'nbest': params.get('nbest', self.nbest), 'workflow': params.get('workflow', self.workflow), 'show_language': params.get('show_language', self.show_language), 'show_utterances': params.get('show_utterances', self.show_utterances), 'result_type': params.get('result_type', self.result_type), 'sequence': params.get('sequence', 1) }, 'audio': { 'format': params.get('format', self.format), 'rate': params.get('rate', self.rate), 'language': params.get('language', self.language), 'bits': params.get('bits', self.bits), 'channel': params.get('channel', self.channel), 'codec': params.get('codec', self.codec) } } return req @staticmethod def slice_data(data: bytes, chunk_size: int) -> (list, bool): """ slice data :param data: wav data :param chunk_size: the segment size in one request :return: segment data, last flag """ data_len = len(data) offset = 0 while offset + chunk_size < data_len: yield data[offset: offset + chunk_size], False offset += chunk_size else: yield data[offset: data_len], True def _real_processor(self, request_params: dict) -> dict: pass def token_auth(self): return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)} def signature_auth(self, data): header_dicts = { 'Custom': 'auth_custom', } url_parse = urlparse(self.volcanic_api_url) input_str = 'GET {} HTTP/1.1\n'.format(url_parse.path) auth_headers = 'Custom' for header in auth_headers.split(','): input_str += '{}\n'.format(header_dicts[header]) input_data = bytearray(input_str, 'utf-8') input_data += data mac = base64.urlsafe_b64encode( hmac.new(self.secret.encode('utf-8'), input_data, digestmod=sha256).digest()) header_dicts['Authorization'] = 'HMAC256; access_token="{}"; mac="{}"; h="{}"'.format(self.volcanic_token, str(mac, 'utf-8'), auth_headers) return header_dicts async def segment_data_processor(self, wav_data: bytes, segment_size: int): reqid = str(uuid.uuid7()) # 构建 full client request,并序列化压缩 request_params = self.construct_request(reqid) payload_bytes = str.encode(json.dumps(request_params)) payload_bytes = gzip.compress(payload_bytes) full_client_request = bytearray(generate_full_default_header()) full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big')) # payload size(4 bytes) full_client_request.extend(payload_bytes) # payload header = None if self.auth_method == "token": header = self.token_auth() elif self.auth_method == "signature": header = self.signature_auth(full_client_request) async with websockets.connect(self.volcanic_api_url, additional_headers=header, max_size=1000000000, ssl=ssl_context) as ws: # 发送 full client request await ws.send(full_client_request) res = await ws.recv() result = parse_response(res) if 'payload_msg' in result and result['payload_msg']['code'] != self.success_code: raise Exception( f"Error code: {result['payload_msg']['code']}, message: {result['payload_msg']['message']}") for seq, (chunk, last) in enumerate(VolcanicEngineSpeechToText.slice_data(wav_data, segment_size), 1): # if no compression, comment this line payload_bytes = gzip.compress(chunk) audio_only_request = bytearray(generate_audio_default_header()) if last: audio_only_request = bytearray(generate_last_audio_default_header()) audio_only_request.extend((len(payload_bytes)).to_bytes(4, 'big')) # payload size(4 bytes) audio_only_request.extend(payload_bytes) # payload # 发送 audio-only client request await ws.send(audio_only_request) res = await ws.recv() result = parse_response(res) if 'payload_msg' in result and result['payload_msg']['code'] != self.success_code: return result return result['payload_msg']['result'][0]['text'] def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f: self.speech_to_text(f) def speech_to_text(self, file): data = file.read() audio_data = bytes(data) if self.format == "mp3": segment_size = self.mp3_seg_size return asyncio.run(self.segment_data_processor(audio_data, segment_size)) if self.format != "wav": raise Exception("format should in wav or mp3") nchannels, sampwidth, framerate, nframes, wav_len = read_wav_info( audio_data) size_per_sec = nchannels * sampwidth * framerate segment_size = int(size_per_sec * self.seg_duration / 1000) return asyncio.run(self.segment_data_processor(audio_data, segment_size)) ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/tti.py ================================================ # coding=utf-8 ''' requires Python 3.6 or later pip install asyncio pip install websockets ''' from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage from volcenginesdkarkruntime import Ark class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage): api_key: str api_base: str model_version: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model_version = kwargs.get('model_version') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return VolcanicEngineTextToImage( model_version=model_name, api_key=model_credential.get('api_key'), api_base=model_credential.get('volcanic_api_url') or 'https://ark-api.volcengine.com', **optional_params ) def check_auth(self): return True def generate_image(self, prompt: str, negative_prompt: str = None): client = Ark( # 此为默认路径,您可根据业务所在地域进行配置 base_url=self.api_base, # 从环境变量中获取您的 API Key。此为默认方式,您可根据需要进行修改 api_key=self.api_key, ) file_urls = [] imagesResponse = client.images.generate( model=self.model_version, prompt=prompt, **self.params ) if imagesResponse.data[0].url: file_urls.append(imagesResponse.data[0].url) elif imagesResponse.data[0].b64_json: file_urls.append(imagesResponse.data[0].b64_json) return file_urls ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py ================================================ # coding=utf-8 ''' requires Python 3.6 or later pip install asyncio pip install websockets ''' import asyncio import copy import gzip import json import re import ssl import requests import uuid_utils.compat as uuid from typing import Dict import websockets from django.utils.translation import gettext as _ from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech MESSAGE_TYPES = {11: "audio-only server response", 12: "frontend server response", 15: "error message from server"} MESSAGE_TYPE_SPECIFIC_FLAGS = {0: "no sequence number", 1: "sequence number > 0", 2: "last message from server (seq < 0)", 3: "sequence number < 0"} MESSAGE_SERIALIZATION_METHODS = {0: "no serialization", 1: "JSON", 15: "custom type"} MESSAGE_COMPRESSIONS = {0: "no compression", 1: "gzip", 15: "custom compression method"} # version: b0001 (4 bits) # header size: b0001 (4 bits) # message type: b0001 (Full client request) (4bits) # message type specific flags: b0000 (none) (4bits) # message serialization method: b0001 (JSON) (4 bits) # message compression: b0001 (gzip) (4bits) # reserved data: 0x00 (1 byte) default_header = bytearray(b'\x11\x10\x11\x00') ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE class VolcanicEngineTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): volcanic_app_id: str volcanic_cluster: str volcanic_api_url: str volcanic_token: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.volcanic_api_url = kwargs.get('volcanic_api_url') self.volcanic_token = kwargs.get('volcanic_token') self.volcanic_app_id = kwargs.get('volcanic_app_id') self.volcanic_cluster = kwargs.get('volcanic_cluster') self.params = kwargs.get('params') @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice_type': 'zh_female_cancan_mars_bigtts', 'speed_ratio': 1.0}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return VolcanicEngineTextToSpeech( volcanic_api_url=model_credential.get('volcanic_api_url'), volcanic_token=model_credential.get('volcanic_token'), volcanic_app_id=model_credential.get('volcanic_app_id'), volcanic_cluster=model_credential.get('volcanic_cluster'), **optional_params ) def check_auth(self): self.text_to_speech(_('Hello')) def text_to_speech(self, text): request_json = { "app": { "appid": self.volcanic_app_id, "token": "access_token", "cluster": self.volcanic_cluster }, "user": { "uid": "uid" }, "audio": { "encoding": "mp3", "volume_ratio": 1.0, "pitch_ratio": 1.0, } | self.params, "request": { "reqid": str(uuid.uuid7()), "text": '', "text_type": "plain", "operation": "xxx" } } text = _remove_empty_lines(text) return asyncio.run(self.submit(request_json, text)) def is_cache_model(self): return False def token_auth(self): return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)} async def submit(self, request_json, text): submit_request_json = copy.deepcopy(request_json) submit_request_json["request"]["operation"] = "submit" header = {"Authorization": f"Bearer; {self.volcanic_token}"} result = b'' async with websockets.connect(self.volcanic_api_url, additional_headers=header, ping_interval=None, ssl=ssl_context) as ws: lines = [text[i:i + 200] for i in range(0, len(text), 200)] for line in lines: if self.is_table_format_chars_only(line): continue submit_request_json["request"]["reqid"] = str(uuid.uuid7()) submit_request_json["request"]["text"] = line payload_bytes = str.encode(json.dumps(submit_request_json)) payload_bytes = gzip.compress(payload_bytes) # if no compression, comment this line full_client_request = bytearray(default_header) full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big')) # payload size(4 bytes) full_client_request.extend(payload_bytes) # payload await ws.send(full_client_request) result += await self.parse_response(ws) return result @staticmethod def is_table_format_chars_only(s): # 检查是否仅包含 "|", "-", 和空格字符 return bool(s) and re.fullmatch(r'[|\-\s]+', s) @staticmethod async def parse_response(ws): result = b'' while True: res = await ws.recv() protocol_version = res[0] >> 4 header_size = res[0] & 0x0f message_type = res[1] >> 4 message_type_specific_flags = res[1] & 0x0f serialization_method = res[2] >> 4 message_compression = res[2] & 0x0f reserved = res[3] header_extensions = res[4:header_size * 4] payload = res[header_size * 4:] if header_size != 1: # print(f" Header extensions: {header_extensions}") pass if message_type == 0xb: # audio-only server response if message_type_specific_flags == 0: # no sequence number as ACK continue else: sequence_number = int.from_bytes(payload[:4], "big", signed=True) payload_size = int.from_bytes(payload[4:8], "big", signed=False) payload = payload[8:] result += payload if sequence_number < 0: break else: continue elif message_type == 0xf: code = int.from_bytes(payload[:4], "big", signed=False) msg_size = int.from_bytes(payload[4:8], "big", signed=False) error_msg = payload[8:] if message_compression == 1: error_msg = gzip.decompress(error_msg) error_msg = str(error_msg, "utf-8") raise Exception(f"Error code: {code}, message: {error_msg}") elif message_type == 0xc: msg_size = int.from_bytes(payload[:4], "big", signed=False) payload = payload[4:] if message_compression == 1: payload = gzip.decompress(payload) else: break return result ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/model/ttv.py ================================================ import base64 import time from typing import Dict, Optional from models_provider.base_model_provider import MaxKBBaseModel from models_provider.base_ttv import BaseGenerationVideo from common.utils.logger import maxkb_logger from volcenginesdkarkruntime import Ark class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): api_key: str model_name: str params: dict max_retries: int = 3 retry_delay: int = 5 # seconds def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model_name = kwargs.get('model_name') self.params = kwargs.get('params', {}) self.retry_delay = 5 @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return GenerationVideoModel( model_name=model_name, api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): return True def _build_prompt(self, prompt: str) -> str: """拼接参数到 prompt 文本""" param_map = { "ratio": "rt", "duration": "dur", "framespersecond": "fps", "resolution": "rs", "watermark": "wm", "camerafixed": "cf", } for key, value in self.params.items(): if key in param_map: prompt += f" --{param_map[key]} {value}" return prompt def _poll_task(self, client: Ark, task_id: str, max_wait: int = 60, interval: int = 5): """轮询任务状态,直到完成或超时""" elapsed = 0 while elapsed < max_wait: result = client.content_generation.tasks.get(task_id=task_id) status = getattr(result, "status", None) maxkb_logger.info(f"[ArkVideo] Task {task_id} status={status}") if status in ("succeeded", "failed", "cancelled"): return result time.sleep(interval) elapsed += interval maxkb_logger.warning(f"[ArkVideo] Task {task_id} wait timeout") return None # --- 通用异步生成函数 --- def generate_video(self, prompt, negative_prompt=None, first_frame_url=None, last_frame_url=None, **kwargs): client = Ark(api_key=self.api_key) # 根据params设置其他参数 豆包的参数和别的不一样 需要拼接在text里 # --rt 16:9 --dur 5 --fps 24 --rs 720p --wm true --cf false prompt = self._build_prompt(prompt) content = [{"type": "text", "text": prompt}] if first_frame_url: content.append({ "type": "image_url", "image_url": { "url": first_frame_url }, "role": "first_frame" }) if last_frame_url: content.append({ "type": "image_url", "image_url": { "url": last_frame_url }, "role": "last_frame" }) create_result = client.content_generation.tasks.create( model=self.model_name, content=content ) task = client.content_generation.tasks.create(model=self.model_name, content=content) task_id = task.id maxkb_logger.info(f"[ArkVideo] Created task {task_id}") # 轮询获取结果 result = self._poll_task(client, task_id) if not result: return {"status": "timeout", "task_id": task_id} try: if getattr(result, "status", None) in ("succeeded", "failed", "cancelled"): client.content_generation.tasks.delete(task_id=task_id) maxkb_logger.info(f"[ArkVideo] Deleted task {task_id}") except Exception as e: maxkb_logger.error(f"[ArkVideo] Failed to delete task {task_id}: {e}") raise e maxkb_logger.info("视频地址", result.content.video_url) return result.content.video_url ================================================ FILE: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ @Project :MaxKB @File :gemini_model_provider.py @Author :Brian Yang @Date :5/13/24 7:47 AM """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ ModelInfoManage from models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential from models_provider.impl.volcanic_engine_model_provider.credential.bigModel_stt import \ VolcanicEngineBigModelSTTModelCredential from models_provider.impl.volcanic_engine_model_provider.credential.embedding import VolcanicEmbeddingCredential from models_provider.impl.volcanic_engine_model_provider.credential.image import \ VolcanicEngineImageModelCredential from models_provider.impl.volcanic_engine_model_provider.credential.tti import VolcanicEngineTTIModelCredential from models_provider.impl.volcanic_engine_model_provider.credential.tts import VolcanicEngineTTSModelCredential from models_provider.impl.volcanic_engine_model_provider.credential.ttv import VolcanicEngineTTVModelCredential from models_provider.impl.volcanic_engine_model_provider.model.bigModel_stt import VolcanicEngineBigModelSpeechToText from models_provider.impl.volcanic_engine_model_provider.model.embedding import VolcanicEngineEmbeddingModel from models_provider.impl.volcanic_engine_model_provider.model.image import VolcanicEngineImage from models_provider.impl.volcanic_engine_model_provider.model.llm import VolcanicEngineChatModel from models_provider.impl.volcanic_engine_model_provider.credential.stt import VolcanicEngineSTTModelCredential from models_provider.impl.volcanic_engine_model_provider.model.stt import VolcanicEngineSpeechToText from models_provider.impl.volcanic_engine_model_provider.model.tti import VolcanicEngineTextToImage from models_provider.impl.volcanic_engine_model_provider.model.tts import VolcanicEngineTextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ from models_provider.impl.volcanic_engine_model_provider.model.ttv import GenerationVideoModel volcanic_engine_llm_model_credential = OpenAILLMModelCredential() volcanic_engine_stt_model_credential = VolcanicEngineSTTModelCredential() volcanic_engine_big_stt_model_credential = VolcanicEngineBigModelSTTModelCredential() volcanic_engine_tts_model_credential = VolcanicEngineTTSModelCredential() volcanic_engine_image_model_credential = VolcanicEngineImageModelCredential() volcanic_engine_tti_model_credential = VolcanicEngineTTIModelCredential() model_info_list = [ ModelInfo('ep-xxxxxxxxxx-yyyy', _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'), ModelTypeConst.LLM, volcanic_engine_llm_model_credential, VolcanicEngineChatModel ), ModelInfo('ep-xxxxxxxxxx-yyyy', _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'), ModelTypeConst.IMAGE, volcanic_engine_image_model_credential, VolcanicEngineImage ), ModelInfo('asr', '', ModelTypeConst.STT, volcanic_engine_stt_model_credential, VolcanicEngineSpeechToText ), ModelInfo('bigmodel', '', ModelTypeConst.STT, volcanic_engine_big_stt_model_credential, VolcanicEngineBigModelSpeechToText ), ModelInfo('tts', '', ModelTypeConst.TTS, volcanic_engine_tts_model_credential, VolcanicEngineTextToSpeech ), ModelInfo('doubao-seedream-3-0-t2i-250415', _(''), ModelTypeConst.TTI, volcanic_engine_tti_model_credential, VolcanicEngineTextToImage ), ] open_ai_embedding_credential = VolcanicEmbeddingCredential() model_info_embedding_list = [ ModelInfo('ep-xxxxxxxxxx-yyyy', _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'), ModelTypeConst.EMBEDDING, open_ai_embedding_credential, VolcanicEngineEmbeddingModel) ] ttv_credential = VolcanicEngineTTVModelCredential() model_info_ttv_list = [ ModelInfo('doubao-seedance-1-0-pro-250528', _(''), ModelTypeConst.TTV, ttv_credential, GenerationVideoModel) , ModelInfo('doubao-seedance-1-0-lite-t2v-250428', _(''), ModelTypeConst.TTV, ttv_credential, GenerationVideoModel) , ModelInfo('wan2-1-14b-t2v-250225', _(''), ModelTypeConst.TTV, ttv_credential, GenerationVideoModel) ] model_info_itv_list = [ ModelInfo('doubao-seedance-1-0-pro-250528', _(''), ModelTypeConst.ITV, ttv_credential, GenerationVideoModel), ModelInfo('doubao-seedance-1-0-lite-i2v-250428', _(''), ModelTypeConst.ITV, ttv_credential, GenerationVideoModel), ModelInfo('wan2-1-14b-i2v-250225', _(''), ModelTypeConst.ITV, ttv_credential, GenerationVideoModel), ModelInfo('wan2-1-14b-flf2v-250417', _(''), ModelTypeConst.ITV, ttv_credential, GenerationVideoModel), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info(model_info_list[0]) .append_default_model_info(model_info_list[1]) .append_default_model_info(model_info_list[2]) .append_default_model_info(model_info_list[3]) .append_default_model_info(model_info_list[4]) .append_default_model_info(model_info_list[5]) .append_model_info_list(model_info_embedding_list) .append_default_model_info(model_info_embedding_list[0]) .append_model_info_list(model_info_ttv_list) .append_default_model_info(model_info_ttv_list[0]) .append_model_info_list(model_info_itv_list) .append_default_model_info(model_info_itv_list[0]) .build() ) class VolcanicEngineModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'volcanic_engine_model_provider', 'icon', 'volcanic_engine_icon_svg'))) ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2023/10/31 17:16 @desc: """ ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/17 15:40 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class QianfanEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): api_version = model_credential.get('api_version', 'v1') model = provider.get_model(model_type, model_name, model_credential, **model_params) if api_version == 'v1': model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) model_info = [model.lower() for model in model.client.models()] if not model_info.__contains__(model_name.lower()): raise AppApiException(ValidCode.valid_error.value, _('{model_name} The model does not support').format(model_name=model_name)) required_keys = ['qianfan_ak', 'qianfan_sk'] if api_version == 'v2': required_keys = ['api_base', 'qianfan_ak'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): api_version = model.get('api_version', 'v1') if api_version == 'v1': return {**model, 'qianfan_sk': super().encryption(model.get('qianfan_sk', ''))} else: # v2 return {**model, 'qianfan_ak': super().encryption(model.get('qianfan_ak', ''))} api_version = forms.Radio('API Version', required=True, text_field='label', value_field='value', option_list=[ {'label': 'v1', 'value': 'v1'}, {'label': 'v2', 'value': 'v2'} ], default_value='v1', provider='', method='', ) # v2版本字段 api_base = forms.TextInputField("API URL", required=True, relation_show_field_dict={"api_version": ["v2"]}) # v1版本字段 qianfan_ak = forms.PasswordInputField('API Key', required=True) qianfan_sk = forms.PasswordInputField("Secret Key", required=True, relation_show_field_dict={"api_version": ["v1"]}) ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/12 10:19 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class WenxinLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.95, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=2, _max=100000, _step=1, precision=0) class WenxinLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): # 根据api_version检查必需字段 api_version = model_credential.get('api_version', 'v1') model = provider.get_model(model_type, model_name, model_credential, **model_params) if api_version == 'v1': model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) model_info = [model.lower() for model in model.client.models()] if not model_info.__contains__(model_name.lower()): raise AppApiException(ValidCode.valid_error.value, gettext('{model_name} The model does not support').format(model_name=model_name)) required_keys = ['api_key', 'secret_key'] if api_version == 'v2': required_keys = ['api_base', 'api_key'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model.invoke( [HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) raise e return True def encryption_dict(self, model_info: Dict[str, object]): # 根据api_version加密不同字段 api_version = model_info.get('api_version', 'v1') if api_version == 'v1': return {**model_info, 'secret_key': super().encryption(model_info.get('secret_key', ''))} else: # v2 return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} def build_model(self, model_info: Dict[str, object]): api_version = model_info.get('api_version', 'v1') # 根据api_version检查必需字段 if api_version == 'v1': for key in ['api_version', 'api_key', 'secret_key', 'model']: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_key = model_info.get('api_key') self.secret_key = model_info.get('secret_key') else: # v2 for key in ['api_version', 'api_base', 'api_key', 'model', ]: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_base = model_info.get('api_base') self.api_key = model_info.get('api_key') return self # 动态字段定义 - 根据api_version显示不同字段 api_version = forms.Radio('API Version', required=True, text_field='label', value_field='value', option_list=[ {'label': 'v1', 'value': 'v1'}, {'label': 'v2', 'value': 'v2'} ], default_value='v1', provider='', method='', ) # v2版本字段 api_base = forms.TextInputField("API URL", required=True, relation_show_field_dict={"api_version": ["v2"]}) # v1版本字段 api_key = forms.PasswordInputField('API Key', required=True) secret_key = forms.PasswordInputField("Secret Key", required=True, relation_show_field_dict={"api_version": ["v1"]}) def get_model_params_setting_form(self, model_name): return WenxinLLMModelParams() ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/17 16:48 @desc: """ from typing import Dict, List from langchain_community.embeddings import QianfanEmbeddingsEndpoint import openai from models_provider.base_model_provider import MaxKBBaseModel class QianfanV1Embeddings(MaxKBBaseModel, QianfanEmbeddingsEndpoint): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return QianfanV1Embeddings( model=model_name, qianfan_ak=model_credential.get('qianfan_ak'), qianfan_sk=model_credential.get('qianfan_sk'), ) class QianfanV2EmbeddingModel(MaxKBBaseModel): model_name: str @staticmethod def is_cache_model(): return False def __init__(self, api_key, base_url, model_name: str): self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings self.model_name = model_name @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return QianfanV2EmbeddingModel( api_key=model_credential.get('qianfan_ak'), model_name=model_name, base_url=model_credential.get('api_base'), ) def embed_query(self, text: str): res = self.embed_documents([text]) return res[0] def embed_documents( self, texts: List[ str], ) -> List[List[float]]: res = self.client.create(input=texts, model=self.model_name, encoding_format="float") return [e.embedding for e in res.data] class QianfanEmbeddings(MaxKBBaseModel): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): api_version = model_credential.get('api_version', 'v1') if api_version == "v1": return QianfanV1Embeddings.new_instance(model_type, model_name, model_credential, **model_kwargs) elif api_version == "v2": return QianfanV2EmbeddingModel.new_instance(model_type, model_name, model_credential, **model_kwargs) ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2023/11/10 17:45 @desc: """ from typing import List, Dict, Optional, Any, Iterator from langchain_community.chat_models.baidu_qianfan_endpoint import _convert_dict_to_message, QianfanChatEndpoint from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.messages import ( AIMessageChunk, BaseMessage, ) from langchain_core.outputs import ChatGenerationChunk from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class QianfanChatModelQianfan(MaxKBBaseModel, QianfanChatEndpoint): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return QianfanChatModelQianfan(model=model_name, qianfan_ak=model_credential.get('api_key'), qianfan_sk=model_credential.get('secret_key'), streaming=model_kwargs.get('streaming', False), init_kwargs=optional_params) usage_metadata: dict = {} def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.usage_metadata def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: return self.usage_metadata.get('prompt_tokens', 0) def get_num_tokens(self, text: str) -> int: return self.usage_metadata.get('completion_tokens', 0) def _stream( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: kwargs = {**self.init_kwargs, **kwargs} params = self._convert_prompt_msg_params(messages, **kwargs) params["stop"] = stop params["stream"] = True for res in self.client.do(**params): if res: msg = _convert_dict_to_message(res) additional_kwargs = msg.additional_kwargs.get("function_call", {}) if msg.content == "" or res.get("body").get("is_end"): token_usage = res.get("body").get("usage") self.usage_metadata = token_usage chunk = ChatGenerationChunk( text=res["result"], message=AIMessageChunk( # type: ignore[call-arg] content=msg.content, role="assistant", additional_kwargs=additional_kwargs, ), generation_info=msg.additional_kwargs, ) if run_manager: run_manager.on_llm_new_token(chunk.text, chunk=chunk) yield chunk class QianfanChatModelOpenai(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return QianfanChatModelOpenai( model=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) class QianfanChatModel(MaxKBBaseModel): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): api_version = model_credential.get('api_version', 'v1') if api_version == "v1": return QianfanChatModelQianfan.new_instance(model_type, model_name, model_credential, **model_kwargs) elif api_version == "v2": return QianfanChatModelOpenai.new_instance(model_type, model_name, model_credential, **model_kwargs) ================================================ FILE: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: wenxin_model_provider.py @date:2023/10/31 16:19 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \ ModelInfoManage from models_provider.impl.wenxin_model_provider.credential.embedding import QianfanEmbeddingCredential from models_provider.impl.wenxin_model_provider.credential.llm import WenxinLLMModelCredential from models_provider.impl.wenxin_model_provider.model.embedding import QianfanEmbeddings from models_provider.impl.wenxin_model_provider.model.llm import QianfanChatModel from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ win_xin_llm_model_credential = WenxinLLMModelCredential() qianfan_embedding_credential = QianfanEmbeddingCredential() model_info_list = [ModelInfo('ERNIE-Bot-4', _('ERNIE-Bot-4 is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'), ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('ERNIE-Bot', _('ERNIE-Bot is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'), ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('ERNIE-Bot-turbo', _('ERNIE-Bot-turbo is a large language model independently developed by Baidu. It covers massive Chinese data, has stronger capabilities in dialogue Q&A, content creation and generation, and has a faster response speed.'), ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('qianfan-chinese-llama-2-13b', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('ernie-4.5-turbo-32k', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('ernie-speed-8k', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel), ModelInfo('ernie-4.5-0.3b', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel) ] embedding_model_info_list = [ModelInfo('Embedding-V1', _('Embedding-V1 is a text representation model based on Baidu Wenxin large model technology. It can convert text into a vector form represented by numerical values and can be used in text retrieval, information recommendation, knowledge mining and other scenarios. Embedding-V1 provides the Embeddings interface, which can generate corresponding vector representations based on input content. You can call this interface to input text into the model and obtain the corresponding vector representation for subsequent text processing and analysis.'), ModelTypeConst.EMBEDDING, qianfan_embedding_credential, QianfanEmbeddings), ModelInfo('tao-8k', '', ModelTypeConst.EMBEDDING, qianfan_embedding_credential, QianfanEmbeddings), ModelInfo('bge-large-zh', '', ModelTypeConst.EMBEDDING, qianfan_embedding_credential, QianfanEmbeddings) ] model_info_manage = ModelInfoManage.builder().append_model_info_list(model_info_list).append_default_model_info( ModelInfo('ERNIE-Bot-4', _('ERNIE-Bot-4 is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'), ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel)).append_model_info_list(embedding_model_info_list).append_default_model_info( embedding_model_info_list[0]).build() class WenxinModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_wenxin_provider', name=_('Thousand sails large model'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'wenxin_model_provider', 'icon', 'azure_icon_svg'))) ================================================ FILE: apps/models_provider/impl/xf_model_provider/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/04/19 15:55 @desc: """ ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/17 15:40 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class XFEmbeddingCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) self.valid_form(model_credential) try: model = provider.get_model(model_type, model_name, model_credential) model.embed_query(_('Hello')) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} base_url = forms.TextInputField('API URL', required=True, default_value="https://emb-cn-huabei-1.xf-yun.com/") spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict from django.utils.translation import gettext as _ from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.xf_model_provider.model.image import ImageMessage from common.utils.logger import maxkb_logger class XunFeiImageModelCredential(BaseForm, BaseModelCredential): spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://spark-api.cn-huabei-1.xf-yun.com/v2.1/image') spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/img_1.png', 'rb') as f: message_list = [ImageMessage(str(base64.b64encode(f.read()), 'utf-8')), HumanMessage(_('Please outline this picture'))] model.stream(message_list) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/12 10:29 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class XunFeiLLMModelGeneralParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.5, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=4096, _min=1, _max=100000, _step=1, precision=0) class XunFeiLLMModelProParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.5, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=4096, _min=1, _max=100000, _step=1, precision=0) class XunFeiLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} spark_api_url = forms.TextInputField('API URL', required=True) spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def get_model_params_setting_form(self, model_name): if model_name == 'general' or model_name == 'pro-128k': return XunFeiLLMModelGeneralParams() return XunFeiLLMModelProParams() ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class XunFeiSTTModelParams(BaseForm): language = forms.TextInputField( TooltipLabel(_('language'), _('If not passed, the default value is zh_cn')), required=True, default_value='zh_cn' ) domain = forms.TextInputField( TooltipLabel(_('domain'), _('If not passed, the default value is iat')), required=True, default_value='iat' ) accent = forms.TextInputField( TooltipLabel(_('accent'), _('If not passed, the default value is mandarin')), required=True, default_value='mandarin' ) class XunFeiSTTModelCredential(BaseForm, BaseModelCredential): spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://iat-api.xfyun.cn/v2/iat') spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): return XunFeiSTTModelParams() ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/tts/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/12/10 14:13 @desc: """ from .tts import * from .default_tts import * from .super_humanoid_tts import * ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/tts/default_tts.py ================================================ # coding=utf-8 """ 讯飞 TTS 工厂类 Credential,根据 api_version 路由到具体 Credential """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class XunFeiDefaultTTSModelCredential(BaseForm, BaseModelCredential): """讯飞 TTS 工厂类 Credential,根据 api_version 参数路由到具体实现""" api_version = forms.SingleSelect( _("API Version"), required=True, text_field='label', value_field='value', default_value='online', option_list=[ {'label': _('Online TTS'), 'value': 'online'}, {'label': _('Super Humanoid TTS'), 'value': 'super_humanoid'} ]) spark_api_url = forms.TextInputField(_('API URL'), required=True) spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) api_version = model_credential.get('api_version', 'online') for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): # params 只包含通用参数,vcn 已在 credential 中 return XunFeiDefaultTTSModelParams() class XunFeiDefaultTTSModelParams(BaseForm): """工厂类的参数表单,只包含通用参数""" speed = forms.SliderField( TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')), required=True, default_value=50, _min=1, _max=100, _step=5, precision=1) ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/tts/super_humanoid_tts.py ================================================ # coding=utf-8 """ 讯飞超拟人语音合成 (Super Humanoid TTS) Credential """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class XunFeiSuperHumanoidTTSModelParams(BaseForm): """超拟人语音合成参数""" vcn = forms.SingleSelect( TooltipLabel(_('Speaker'), _('Speaker selection for super-humanoid TTS service')), required=True, default_value='x5_lingxiaoxuan_flow', text_field='label', value_field='value', option_list=[ {'label': _('Super-humanoid: Lingxiaoxuan Flow'), 'value': 'x5_lingxiaoxuan_flow'}, {'label': _('Super-humanoid: Lingyuyan Flow'), 'value': 'x5_lingyuyan_flow'}, {'label': _('Super-humanoid: Lingfeiyi Flow'), 'value': 'x5_lingfeiyi_flow'}, {'label': _('Super-humanoid: Lingxiaoyue Flow'), 'value': 'x5_lingxiaoyue_flow'}, {'label': _('Super-humanoid: Sun Dasheng Flow'), 'value': 'x5_sundasheng_flow'}, {'label': _('Super-humanoid: Lingyuzhao Flow'), 'value': 'x5_lingyuzhao_flow'}, {'label': _('Super-humanoid: Lingxiaotang Flow'), 'value': 'x5_lingxiaotang_flow'}, {'label': _('Super-humanoid: Lingxiaorong Flow'), 'value': 'x5_lingxiaorong_flow'}, {'label': _('Super-humanoid: Xinyun Flow'), 'value': 'x5_xinyun_flow'}, {'label': _('Super-humanoid: Grant (EN)'), 'value': 'x5_EnUs_Grant_flow'}, {'label': _('Super-humanoid: Lila (EN)'), 'value': 'x5_EnUs_Lila_flow'}, {'label': _('Super-humanoid: Lingwanwan Pro'), 'value': 'x6_lingwanwan_pro'}, {'label': _('Super-humanoid: Yiyi Pro'), 'value': 'x6_yiyi_pro'}, {'label': _('Super-humanoid: Huifangnv Pro'), 'value': 'x6_huifangnv_pro'}, {'label': _('Super-humanoid: Lingxiaoying Pro'), 'value': 'x6_lingxiaoying_pro'}, {'label': _('Super-humanoid: Lingfeibo Pro'), 'value': 'x6_lingfeibo_pro'}, {'label': _('Super-humanoid: Lingyuyan Pro'), 'value': 'x6_lingyuyan_pro'}, ]) speed = forms.SliderField( TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')), required=True, default_value=50, _min=1, _max=100, _step=5, precision=1) class XunFeiSuperHumanoidTTSModelCredential(BaseForm, BaseModelCredential): """讯飞超拟人语音合成 Credential""" spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://cbm01.cn-huabei-1.xf-yun.com/v1/private/mcd9m97e6') spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) required_keys = ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret'] for key in required_keys: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): return XunFeiSuperHumanoidTTSModelParams() ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/tts/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class XunFeiTTSModelGeneralParams(BaseForm): vcn = forms.SingleSelect( TooltipLabel(_('Speaker'), _('Speaker, optional value: Please go to the console to add a trial or purchase speaker. After adding, the speaker parameter value will be displayed.')), required=True, default_value='xiaoyan', text_field='value', value_field='value', option_list=[ {'text': _('iFlytek Xiaoyan'), 'value': 'xiaoyan'}, {'text': _('iFlytek Xujiu'), 'value': 'aisjiuxu'}, {'text': _('iFlytek Xiaoping'), 'value': 'aisxping'}, {'text': _('iFlytek Xiaojing'), 'value': 'aisjinger'}, {'text': _('iFlytek Xuxiaobao'), 'value': 'aisbabyxu'}, ]) speed = forms.SliderField( TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')), required=True, default_value=50, _min=1, _max=100, _step=5, precision=1) class XunFeiTTSModelCredential(BaseForm, BaseModelCredential): spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://tts-api.xfyun.cn/v2/tts') spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): return XunFeiTTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/xf_model_provider/credential/zh_en_stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class ZhEnXunFeiSTTModelCredential(BaseForm, BaseModelCredential): spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://iat.xf-yun.com/v1') spark_app_id = forms.TextInputField('APP ID', required=True) spark_api_key = forms.PasswordInputField("API Key", required=True) spark_api_secret = forms.PasswordInputField('API Secret', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/embedding.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: embedding.py @date:2024/10/17 15:29 @desc: """ import base64 import json from typing import Dict, Optional from langchain_community.embeddings import SparkLLMTextEmbeddings from numpy import ndarray from models_provider.base_model_provider import MaxKBBaseModel import time import json import base64 import numpy as np import threading import queue _task_queue = queue.Queue() def _worker(): while True: message, future = _task_queue.get() for i in range(3): try: data = json.loads(message) code = data["header"]["code"] if code != 0: raise Exception(f"Request error: {code}, {data}") text_base = data["payload"]["feature"]["text"] text_data = base64.b64decode(text_base) dt = np.dtype(np.float32) dt = dt.newbyteorder("<") text = np.frombuffer(text_data, dtype=dt) if len(text) > 2560: array = text[:2560] else: array = text future["result"] = array future["event"].set() break except Exception as e: if i == 2: future["error"] = e future["event"].set() else: time.sleep(0.5) time.sleep(0.5) # QPS=2 threading.Thread(target=_worker, daemon=True).start() class XFEmbedding(MaxKBBaseModel, SparkLLMTextEmbeddings): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return XFEmbedding( base_url=model_credential.get('base_url'), spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret') ) @staticmethod def _parser_message( message: str, ) -> Optional[ndarray]: future = { "event": threading.Event(), "result": None, "error": None } _task_queue.put((message, future)) future["event"].wait() if future["error"]: raise future["error"] return future["result"] ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/image.py ================================================ # coding=utf-8 import base64 import os from typing import Dict, Any, List, Optional, Iterator #from docutils.utils import SystemMessage from langchain_community.chat_models.sparkllm import ChatSparkLLM, _convert_delta_to_message_chunk from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.messages import BaseMessage, ChatMessage, HumanMessage, AIMessage, AIMessageChunk from langchain_core.outputs import ChatGenerationChunk from models_provider.base_model_provider import MaxKBBaseModel class ImageMessage(HumanMessage): content: str def convert_message_to_dict(message: BaseMessage) -> dict: message_dict: Dict[str, Any] if isinstance(message, ChatMessage): message_dict = {"role": "user", "content": message.content} elif isinstance(message, ImageMessage): message_dict = {"role": "user", "content": message.content, "content_type": "image"} elif isinstance(message, HumanMessage): message_dict = {"role": "user", "content": message.content} elif isinstance(message, AIMessage): message_dict = {"role": "assistant", "content": message.content} if "function_call" in message.additional_kwargs: message_dict["function_call"] = message.additional_kwargs["function_call"] # If function call only, content is None not empty string if message_dict["content"] == "": message_dict["content"] = None if "tool_calls" in message.additional_kwargs: message_dict["tool_calls"] = message.additional_kwargs["tool_calls"] # If tool calls only, content is None not empty string if message_dict["content"] == "": message_dict["content"] = None # elif isinstance(message, SystemMessage): # message_dict = {"role": "system", "content": message.content} else: raise ValueError(f"Got unknown type {message}") return message_dict class XFSparkImage(MaxKBBaseModel, ChatSparkLLM): spark_app_id: str spark_api_key: str spark_api_secret: str spark_api_url: str @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return XFSparkImage( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), **optional_params ) @staticmethod def generate_message(prompt: str, image) -> list[BaseMessage]: if image is None: cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/img_1.png', 'rb') as f: base64_image = base64.b64encode(f.read()).decode("utf-8") return [ImageMessage(f'data:image/jpeg;base64,{base64_image}'), HumanMessage(prompt)] return [HumanMessage(prompt)] def _stream( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: default_chunk_class = AIMessageChunk self.client.arun( [convert_message_to_dict(m) for m in messages], self.spark_user_id, self.model_kwargs, streaming=True, ) for content in self.client.subscribe(timeout=self.request_timeout): if "data" not in content: continue delta = content["data"] chunk = _convert_delta_to_message_chunk(delta, default_chunk_class) cg_chunk = ChatGenerationChunk(message=chunk) if run_manager: run_manager.on_llm_new_token(str(chunk.content), chunk=cg_chunk) yield cg_chunk @staticmethod def is_cache_model(): return False ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py.py @date:2024/04/19 15:55 @desc: """ from typing import List, Optional, Any, Iterator, Dict from langchain_community.chat_models.sparkllm import \ ChatSparkLLM, convert_message_to_dict, _convert_delta_to_message_chunk from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.messages import BaseMessage, AIMessageChunk from langchain_core.outputs import ChatGenerationChunk from models_provider.base_model_provider import MaxKBBaseModel class XFChatSparkLLM(MaxKBBaseModel, ChatSparkLLM): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return XFChatSparkLLM( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), spark_llm_domain=model_name, streaming=model_kwargs.get('streaming', False), **optional_params ) usage_metadata: dict = {} def get_last_generation_info(self) -> Optional[Dict[str, Any]]: return self.usage_metadata def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: return self.usage_metadata.get('prompt_tokens', 0) def get_num_tokens(self, text: str) -> int: return self.usage_metadata.get('completion_tokens', 0) def _stream( self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: default_chunk_class = AIMessageChunk self.client.arun( [convert_message_to_dict(m) for m in messages], self.spark_user_id, self.model_kwargs, True, ) for content in self.client.subscribe(timeout=self.request_timeout): if "data" in content: delta = content["data"] chunk = _convert_delta_to_message_chunk(delta, default_chunk_class) cg_chunk = ChatGenerationChunk(message=chunk) elif "usage" in content: generation_info = content["usage"] self.usage_metadata = generation_info continue else: continue if cg_chunk is not None: if run_manager: run_manager.on_llm_new_token(str(cg_chunk.message.content), chunk=cg_chunk) yield cg_chunk ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/stt.py ================================================ # -*- coding:utf-8 -*- # # 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # import asyncio import base64 import datetime import hashlib import hmac import json import logging import os import ssl from datetime import datetime, UTC from typing import Dict from urllib.parse import urlencode, urlparse import websockets from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText STATUS_FIRST_FRAME = 0 # 第一帧的标识 STATUS_CONTINUE_FRAME = 1 # 中间帧标识 STATUS_LAST_FRAME = 2 # 最后一帧的标识 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE class XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText): spark_app_id: str spark_api_key: str spark_api_secret: str spark_api_url: str params: dict model_name: str def __init__(self, **kwargs): super().__init__(**kwargs) self.spark_api_url = kwargs.get('spark_api_url') self.spark_app_id = kwargs.get('spark_app_id') self.spark_api_key = kwargs.get('spark_api_key') self.spark_api_secret = kwargs.get('spark_api_secret') self.params = kwargs.get('params') self.model_name = kwargs.get('model_name') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return XFSparkSpeechToText( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), params=model_kwargs, model_name=model_name, **optional_params ) # 生成url def create_url(self): url = self.spark_api_url host = urlparse(url).hostname # 生成RFC1123格式的时间戳 gmt_format = '%a, %d %b %Y %H:%M:%S GMT' date = datetime.now(UTC).strftime(gmt_format) # 拼接字符串 signature_origin = "host: " + host + "\n" signature_origin += "date: " + date + "\n" signature_origin += "GET " + "/v2/iat " + "HTTP/1.1" # 进行hmac-sha256进行加密 signature_sha = hmac.new(self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'), digestmod=hashlib.sha256).digest() signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8') authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % ( self.spark_api_key, "hmac-sha256", "host date request-line", signature_sha) authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') # 将请求的鉴权参数组合为字典 v = { "authorization": authorization, "date": date, "host": host } # 拼接鉴权参数,生成url url = url + '?' + urlencode(v) # print("date: ",date) # print("v: ",v) # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致 # print('websocket url :', url) return url def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f: self.speech_to_text(f) def speech_to_text(self, file): async def handle(): async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws: # 发送 full client request await self.send(ws, file) return await self.handle_message(ws) return asyncio.run(handle()) @staticmethod async def handle_message(ws): res = await ws.recv() message = json.loads(res) code = message["code"] sid = message["sid"] if code != 0: errMsg = message["message"] raise Exception(f"sid: {sid} call error: {errMsg} code is: {code}") else: data = message["data"]["result"]["ws"] result = "" for i in data: for w in i["cw"]: result += w["w"] # print("sid:%s call success!,data is:%s" % (sid, json.dumps(data, ensure_ascii=False))) return result # 收到websocket连接建立的处理 async def send(self, ws, file): frameSize = 8000 # 每一帧的音频大小 status = STATUS_FIRST_FRAME # 音频的状态信息,标识音频是第一帧,还是中间帧、最后一帧 allowed_params = {'language', 'domain', 'accent', 'vad_eos', 'dwa', 'pd', 'ptt', 'pcm', 'ltc', 'rlang', 'vinfo', 'nunum', 'speex_size', 'nbest', 'wbest'} business_params = {k: v for k, v in self.params.items() if k in allowed_params} if not business_params: business_params = { "domain": f'{self.model_name}', "language": "zh_cn", "accent": "mandarin", "vinfo": 1, "vad_eos": 10000 } while True: buf = file.read(frameSize) # 文件结束 if not buf: status = STATUS_LAST_FRAME # 第一帧处理 # 发送第一帧音频,带business 参数 # appid 必须带上,只需第一帧发送 if status == STATUS_FIRST_FRAME: d = { "common": {"app_id": self.spark_app_id}, "business": { **business_params }, "data": { "status": 0, "format": "audio/L16;rate=16000", "audio": str(base64.b64encode(buf), 'utf-8'), "encoding": "lame"} } d = json.dumps(d) await ws.send(d) status = STATUS_CONTINUE_FRAME # 中间帧处理 elif status == STATUS_CONTINUE_FRAME: d = {"data": {"status": 1, "format": "audio/L16;rate=16000", "audio": str(base64.b64encode(buf), 'utf-8'), "encoding": "lame"}} await ws.send(json.dumps(d)) # 最后一帧处理 elif status == STATUS_LAST_FRAME: d = {"data": {"status": 2, "format": "audio/L16;rate=16000", "audio": str(base64.b64encode(buf), 'utf-8'), "encoding": "lame"}} await ws.send(json.dumps(d)) break ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/tts/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2025/12/10 14:14 @desc: """ from .super_humanoid_tts import * from .tts import * from .default_tts import * ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/tts/default_tts.py ================================================ # coding=utf-8 """ @project: MaxKB @Author: @file: default_tts.py @date:2025/12/9 @desc: 讯飞 TTS 工厂类,根据 api_version 路由到具体实现 """ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech class XFSparkDefaultTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): """讯飞 TTS 工厂类,根据 api_version 参数路由到具体实现""" def check_auth(self): pass def text_to_speech(self, text): pass @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): from models_provider.impl.xf_model_provider.model.tts import XFSparkTextToSpeech from models_provider.impl.xf_model_provider.model.tts.super_humanoid_tts import XFSparkSuperHumanoidTextToSpeech api_version = model_credential.get('api_version', 'online') if api_version == 'super_humanoid': return XFSparkSuperHumanoidTextToSpeech( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), params = model_kwargs, **model_kwargs ) else: # 在线语音:从 credential 获取 vcn_online return XFSparkTextToSpeech( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), params={key: v for key, v in model_kwargs.items() if not ['parameter', 'streaming', 'model_id', 'use_local'].__contains__(key)}, **model_kwargs ) ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/tts/super_humanoid_tts.py ================================================ # -*- coding:utf-8 -*- # # author: iflytek # # 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # import asyncio import base64 import hashlib import hmac import json import ssl from datetime import datetime, UTC from typing import Dict from urllib.parse import urlencode, urlparse import websockets from django.utils.translation import gettext as _ from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE class XFSparkSuperHumanoidTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): """讯飞超拟人语音合成 (Super Humanoid TTS)""" spark_app_id: str spark_api_key: str spark_api_secret: str spark_api_url: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.spark_api_url = kwargs.get('spark_api_url') self.spark_app_id = kwargs.get('spark_app_id') self.spark_api_key = kwargs.get('spark_api_key') self.spark_api_secret = kwargs.get('spark_api_secret') self.params = kwargs.get('params') or {} @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): # vcn = model_kwargs.get('vcn', 'x5_lingxiaoxuan_flow') params = {} for k, v in model_kwargs.items(): if k not in ['model_id', 'use_local', 'streaming']: params[k] = v return XFSparkSuperHumanoidTextToSpeech( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), params=params, **model_kwargs ) def create_url(self): url = self.spark_api_url host = urlparse(url).hostname gmt_format = '%a, %d %b %Y %H:%M:%S GMT' date = datetime.now(UTC).strftime(gmt_format) signature_origin = f"host: {host}\n" signature_origin += f"date: {date}\n" signature_origin += f"GET {urlparse(url).path} HTTP/1.1" signature_sha = hmac.new( self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'), digestmod=hashlib.sha256 ).digest() signature_sha = base64.b64encode(signature_sha).decode('utf-8') authorization_origin = \ f'api_key="{self.spark_api_key}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha}"' authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode('utf-8') v = { "authorization": authorization, "date": date, "host": host } url = url + '?' + urlencode(v) return url def check_auth(self): self.text_to_speech(_('Hello')) def text_to_speech(self, text): text = _remove_empty_lines(text) async def handle(): try: async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws: await self.send(ws, text) return await self.handle_message(ws) except websockets.exceptions.InvalidStatus as e: if e.response.status_code == 401: raise Exception( _("Authentication failed (HTTP 401). Please check: " "1) API URL is correct for TTS service; " "2) APP ID, API Key, and API Secret are correct; " "3) Your iFlytek account has TTS service enabled.") ) else: raise Exception(f"WebSocket connection failed: HTTP {e.response.status_code}") except Exception as e: if "Authentication failed" in str(e): raise raise Exception(f"iFlytek TTS service error: {str(e)}") return asyncio.run(handle()) @staticmethod async def handle_message(ws): audio_bytes: bytes = b'' while True: res = await ws.recv() message = json.loads(res) if "header" in message and "code" in message["header"]: code = message["header"]["code"] sid = message["header"].get("sid", "unknown") if code != 0: errMsg = message["header"].get("message", "Unknown error") raise Exception(f"sid: {sid} call error: {errMsg} code is: {code}") if "payload" in message and "audio" in message["payload"]: audio = base64.b64decode(message["payload"]["audio"]["audio"]) audio_bytes += audio if message["payload"]["audio"].get("status") == 2: break else: raise Exception( f"Unexpected response from iFlytek API. Response: {json.dumps(message, ensure_ascii=False)}" ) return audio_bytes async def send(self, ws, text): audio_params = { "encoding": self.params.get("encoding", "lame"), "sample_rate": self.params.get("sample_rate", 24000), "channels": self.params.get("channels", 1), "bit_depth": self.params.get("bit_depth", 16), "frame_size": self.params.get("frame_size", 0) } tts_params = { **{key: v for key, v in self.params.items() if not ['parameter', 'streaming', 'model_id', 'use_local'].__contains__(key)}, "vcn": self.params.get("vcn") or "x5_lingxiaoxuan_flow", "audio": audio_params, "volume": self.params.get("volume", 50), "speed": self.params.get("speed", 50), "pitch": self.params.get("pitch", 50) } encoded_text = base64.b64encode(text.encode('utf-8')).decode('utf-8') payload_text_obj = { "encoding": "utf8", "compress": "raw", "format": "plain", "status": 2, "seq": 0, "text": encoded_text } s = {"tts": tts_params} # "parameter": {"oar":"xxxx"} parameter = self.params.get("parameter") or {} d = { "header": {"app_id": self.spark_app_id, "status": 2}, "parameter": {"tts": tts_params} | parameter, "payload": {"text": payload_text_obj} } await ws.send(json.dumps(d)) ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/tts/tts.py ================================================ # -*- coding:utf-8 -*- # # author: iflytek # # 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # import asyncio import base64 import datetime import hashlib import hmac import json import logging import ssl from datetime import datetime, UTC from typing import Dict from urllib.parse import urlencode, urlparse import websockets from django.utils.translation import gettext as _ from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech STATUS_FIRST_FRAME = 0 # 第一帧的标识 STATUS_CONTINUE_FRAME = 1 # 中间帧标识 STATUS_LAST_FRAME = 2 # 最后一帧的标识 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE class XFSparkTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): spark_app_id: str spark_api_key: str spark_api_secret: str spark_api_url: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.spark_api_url = kwargs.get('spark_api_url') self.spark_app_id = kwargs.get('spark_app_id') self.spark_api_key = kwargs.get('spark_api_key') self.spark_api_secret = kwargs.get('spark_api_secret') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'vcn': 'xiaoyan', 'speed': 50}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return XFSparkTextToSpeech( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), **optional_params ) # 生成url def create_url(self): url = self.spark_api_url host = urlparse(url).hostname # 生成RFC1123格式的时间戳 gmt_format = '%a, %d %b %Y %H:%M:%S GMT' date = datetime.now(UTC).strftime(gmt_format) # 拼接字符串 signature_origin = "host: " + host + "\n" signature_origin += "date: " + date + "\n" signature_origin += "GET " + "/v2/tts " + "HTTP/1.1" # 进行hmac-sha256进行加密 signature_sha = hmac.new(self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'), digestmod=hashlib.sha256).digest() signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8') authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % ( self.spark_api_key, "hmac-sha256", "host date request-line", signature_sha) authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') # 将请求的鉴权参数组合为字典 v = { "authorization": authorization, "date": date, "host": host } # 拼接鉴权参数,生成url url = url + '?' + urlencode(v) # print("date: ",date) # print("v: ",v) # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致 # print('websocket url :', url) return url def check_auth(self): self.text_to_speech(_('Hello')) def text_to_speech(self, text): # 使用小语种须使用以下方式,此处的unicode指的是 utf16小端的编码方式,即"UTF-16LE"” # self.Data = {"status": 2, "text": str(base64.b64encode(self.Text.encode('utf-16')), "UTF8")} text = _remove_empty_lines(text) async def handle(): async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws: # 发送 full client request await self.send(ws, text) return await self.handle_message(ws) return asyncio.run(handle()) def is_cache_model(self): return False @staticmethod async def handle_message(ws): audio_bytes: bytes = b'' while True: res = await ws.recv() message = json.loads(res) # print(message) code = message["code"] sid = message["sid"] if code != 0: errMsg = message["message"] raise Exception(f"sid: {sid} call error: {errMsg} code is: {code}") else: audio = message["data"]["audio"] audio = base64.b64decode(audio) audio_bytes += audio # 退出 if message["data"]["status"] == 2: break return audio_bytes async def send(self, ws, text): business = {"aue": "lame", "sfl": 1, "auf": "audio/L16;rate=16000", "tte": "utf8"} d = { "common": {"app_id": self.spark_app_id}, "business": business | self.params, "data": {"status": 2, "text": str(base64.b64encode(text.encode('utf-8')), "UTF8")}, } d = json.dumps(d) await ws.send(d) ================================================ FILE: apps/models_provider/impl/xf_model_provider/model/zh_en_stt.py ================================================ import asyncio import json import base64 import hmac import hashlib import ssl import traceback from typing import Dict from urllib.parse import urlencode from datetime import datetime, timezone, UTC import websockets import os from future.backports.urllib.parse import urlparse from common.utils.logger import maxkb_logger from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE def deep_merge_dict(target_dict, source_dict): if not isinstance(source_dict, dict): return source_dict result = target_dict.copy() if isinstance(target_dict, dict) else {} for key, value in source_dict.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = deep_merge_dict(result[key], value) else: result[key] = value return result class XFZhEnSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText): spark_app_id: str spark_api_key: str spark_api_secret: str spark_api_url: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.spark_api_url = kwargs.get('spark_api_url') self.spark_app_id = kwargs.get('spark_app_id') self.spark_api_key = kwargs.get('spark_api_key') self.spark_api_secret = kwargs.get('spark_api_secret') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return XFZhEnSparkSpeechToText( spark_app_id=model_credential.get('spark_app_id'), spark_api_key=model_credential.get('spark_api_key'), spark_api_secret=model_credential.get('spark_api_secret'), spark_api_url=model_credential.get('spark_api_url'), params=model_kwargs, **model_kwargs ) # 生成url def create_url(self): url = self.spark_api_url host = urlparse(url).hostname gmt_format = '%a, %d %b %Y %H:%M:%S GMT' date = datetime.now(UTC).strftime(gmt_format) # 拼接字符串 signature_origin = "host: " + host + "\n" signature_origin += "date: " + date + "\n" signature_origin += "GET " + "/v1 HTTP/1.1" # 进行hmac-sha256进行加密 signature_sha = hmac.new( self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'), hashlib.sha256 ).digest() signature = base64.b64encode(signature_sha).decode(encoding='utf-8') authorization_origin = ( f'api_key="{self.spark_api_key}", algorithm="hmac-sha256", ' f'headers="host date request-line", signature="{signature}"' ) authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') params = { 'authorization': authorization, 'date': date, 'host': host } auth_url = url + '?' + urlencode(params) return auth_url def check_auth(self): cwd = os.path.dirname(os.path.abspath(__file__)) with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f: self.speech_to_text(f) def speech_to_text(self, audio_file_path): async def handle(): async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws: # print("连接成功") # 发送音频数据 await self.send_audio(ws, audio_file_path) # 接收识别结果 return await self.handle_message(ws) try: return asyncio.run(handle()) except Exception as err: maxkb_logger.error(f"语音识别错误: {str(err)}: {traceback.format_exc()}") raise def merge_params_to_frame(self, frame,params): return deep_merge_dict(frame, params) async def send_audio(self, ws, audio_file): """发送音频数据""" chunk_size = 4000 seq = 1 max_chunks = 10000 while True: chunk = audio_file.read(chunk_size) if not chunk or seq > max_chunks: break chunk_base64 = base64.b64encode(chunk).decode('utf-8') # 第一帧 if seq == 1: frame = { "header": {"app_id": self.spark_app_id, "status": 0}, "parameter": { "iat": { "domain": "slm", "language": "zh_cn", "accent": "mandarin", "eos": 10000, "vinfo": 1, "result": {"encoding": "utf8", "compress": "raw", "format": "json"} } }, "payload": { "audio": { "encoding": "lame", "sample_rate": 16000, "channels": 1, "bit_depth": 16, "seq": seq, "status": 0, "audio": chunk_base64 } } } frame = self.merge_params_to_frame(frame,{key: value for key, value in self.params.items() if not ['model_id', 'use_local', 'streaming'].__contains__(key)}) # 中间帧 else: frame = { "header": {"app_id": self.spark_app_id, "status": 1}, "payload": { "audio": { "encoding": "lame", "sample_rate": 16000, "channels": 1, "bit_depth": 16, "seq": seq, "status": 1, "audio": chunk_base64 } } } frame = self.merge_params_to_frame(frame,{key: value for key, value in self.params.items() if not ['model_id', 'use_local', 'streaming','parameter'].__contains__(key)}) await ws.send(json.dumps(frame)) seq += 1 # 发送结束帧 end_frame = { "header": {"app_id": self.spark_app_id, "status": 2}, "payload": { "audio": { "encoding": "lame", "sample_rate": 16000, "channels": 1, "bit_depth": 16, "seq": seq, "status": 2, "audio": "" } } } end_frame = self.merge_params_to_frame(end_frame,{key: value for key, value in self.params.items() if not ['model_id', 'use_local', 'streaming','parameter'].__contains__(key)}) await ws.send(json.dumps(end_frame)) # 接受信息处理器 async def handle_message(self, ws): result_text = "" while True: try: message = await asyncio.wait_for(ws.recv(), timeout=30.0) data = json.loads(message) if data['header']['code'] != 0: raise Exception("") if 'payload' in data and 'result' in data['payload']: result = data['payload']['result'] text = result.get('text', '') if text: text_data = json.loads(base64.b64decode(text).decode('utf-8')) for ws_item in text_data.get('ws', []): for cw in ws_item.get('cw', []): for sw in cw.get('w', []): result_text += sw if data['header'].get('status') == 2: break except asyncio.TimeoutError: break return result_text ================================================ FILE: apps/models_provider/impl/xf_model_provider/xf_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: xf_model_provider.py @date:2024/04/19 14:47 @desc: """ import os import ssl from common.utils.common import get_file_content from models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \ ModelInfoManage from models_provider.impl.xf_model_provider.credential.embedding import XFEmbeddingCredential from models_provider.impl.xf_model_provider.credential.image import XunFeiImageModelCredential from models_provider.impl.xf_model_provider.credential.llm import XunFeiLLMModelCredential from models_provider.impl.xf_model_provider.credential.stt import XunFeiSTTModelCredential from models_provider.impl.xf_model_provider.credential.tts import XunFeiTTSModelCredential from models_provider.impl.xf_model_provider.credential.tts.super_humanoid_tts import XunFeiSuperHumanoidTTSModelCredential from models_provider.impl.xf_model_provider.credential.tts.default_tts import XunFeiDefaultTTSModelCredential from models_provider.impl.xf_model_provider.credential.zh_en_stt import ZhEnXunFeiSTTModelCredential from models_provider.impl.xf_model_provider.model.embedding import XFEmbedding from models_provider.impl.xf_model_provider.model.llm import XFChatSparkLLM from models_provider.impl.xf_model_provider.model.stt import XFSparkSpeechToText from models_provider.impl.xf_model_provider.model.tts import XFSparkTextToSpeech from models_provider.impl.xf_model_provider.model.tts.super_humanoid_tts import XFSparkSuperHumanoidTextToSpeech from models_provider.impl.xf_model_provider.model.tts.default_tts import XFSparkDefaultTextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ from models_provider.impl.xf_model_provider.model.zh_en_stt import XFZhEnSparkSpeechToText ssl._create_default_https_context = ssl.create_default_context() xunfei_model_credential = XunFeiLLMModelCredential() stt_model_credential = XunFeiSTTModelCredential() zh_en_stt_credential = ZhEnXunFeiSTTModelCredential() image_model_credential = XunFeiImageModelCredential() # TTS credentials tts_model_credential = XunFeiTTSModelCredential() super_humanoid_tts_credential = XunFeiSuperHumanoidTTSModelCredential() default_tts_credential = XunFeiDefaultTTSModelCredential() embedding_model_credential = XFEmbeddingCredential() model_info_list = [ ModelInfo('generalv3.5', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM), ModelInfo('generalv3', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM), ModelInfo('generalv2', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM), ModelInfo('iat', _('Chinese and English recognition'), ModelTypeConst.STT, stt_model_credential, XFSparkSpeechToText), ModelInfo('slm', _('Chinese and English recognition'), ModelTypeConst.STT, zh_en_stt_credential, XFZhEnSparkSpeechToText), # 具体 TTS 模型 ModelInfo('tts', _('Online TTS'), ModelTypeConst.TTS, tts_model_credential, XFSparkTextToSpeech), ModelInfo('tts-super-humanoid', _('Super Humanoid TTS'), ModelTypeConst.TTS, super_humanoid_tts_credential, XFSparkSuperHumanoidTextToSpeech), ModelInfo('embedding', '', ModelTypeConst.EMBEDDING, embedding_model_credential, XFEmbedding) ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info( ModelInfo('generalv3.5', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM)) .append_default_model_info( ModelInfo('iat', _('Chinese and English recognition'), ModelTypeConst.STT, stt_model_credential, XFSparkSpeechToText), ) # default TTS 工厂入口 .append_default_model_info( ModelInfo('default', _('default'), ModelTypeConst.TTS, default_tts_credential, XFSparkDefaultTextToSpeech)) .append_default_model_info( ModelInfo('embedding', '', ModelTypeConst.EMBEDDING, embedding_model_credential, XFEmbedding)) .build() ) class XunFeiModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_xf_provider', name=_('iFlytek Spark'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'xf_model_provider', 'icon', 'xf_icon_svg'))) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/embedding.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.local_model_provider.model.embedding import LocalEmbedding class XinferenceEmbeddingModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key'), 'embedding') except Exception as e: raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid')) exist = provider.get_model_info_by_name(model_list, model_name) model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential) if len(exist) == 0: model.start_down_model_thread() raise AppApiException(ValidCode.model_not_fount, _('The model does not exist, please download the model first')) model.embed_query(_('Hello')) return True def encryption_dict(self, model_info: Dict[str, object]): return model_info def build_model(self, model_info: Dict[str, object]): for key in ['model']: if key not in model_info: raise AppApiException(500, _('{key} is required').format(key=key)) return self api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class XinferenceImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class XinferenceImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return XinferenceImageModelParams() ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/llm.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class XinferenceLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.7, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=800, _min=1, _max=100000, _step=1, precision=0) class XinferenceLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) try: model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key'), model_type) except Exception as e: raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid')) exist = provider.get_model_info_by_name(model_list, model_name) if len(exist) == 0: raise AppApiException(ValidCode.valid_error.value, gettext('The model does not exist, please download the model first')) model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) return True def encryption_dict(self, model_info: Dict[str, object]): return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))} def build_model(self, model_info: Dict[str, object]): for key in ['api_key', 'model']: if key not in model_info: raise AppApiException(500, gettext('{key} is required').format(key=key)) self.api_key = model_info.get('api_key') return self api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return XinferenceLLMModelParams() ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: reranker.py @date:2024/9/10 9:46 @desc: """ from typing import Dict from django.utils.translation import gettext as _ from langchain_core.documents import Document from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode class XInferenceRerankerModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=True): if not model_type == 'RERANKER': raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['server_url']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential) model.compress_documents([Document(page_content=_('Hello'))], _('Hello')) except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model_info: Dict[str, object]): return model_info server_url = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=False) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/stt.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext as _ from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm from models_provider.base_model_provider import BaseModelCredential, ValidCode class XInferenceSTTModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, _('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, _('Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): pass ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class XinferenceTTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('The image generation endpoint allows you to create raw images based on text prompts. The dimensions of the image can be 1024x1024, 1024x1792, or 1792x1024 pixels.')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '1024x1792', 'label': '1024x1792'}, {'value': '1792x1024', 'label': '1792x1024'}, ], text_field='label', value_field='value' ) quality = forms.SingleSelect( TooltipLabel(_('Picture quality'), _('By default, images are generated in standard quality, you can set quality: "hd" to enhance detail. Square, standard quality images are generated fastest.')), required=True, default_value='standard', option_list=[ {'value': 'standard', 'label': 'standard'}, {'value': 'hd', 'label': 'hd'}, ], text_field='label', value_field='value' ) n = forms.SliderField( TooltipLabel(_('Number of pictures'), _('You can request 1 image at a time (requesting more images by making parallel requests), or up to 10 images at a time using the n parameter.')), required=True, default_value=1, _min=1, _max=10, _step=1, precision=0) class XinferenceTextToImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return XinferenceTTIModelParams() ================================================ FILE: apps/models_provider/impl/xinference_model_provider/credential/tts.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode class XInferenceTTSModelGeneralParams(BaseForm): # ['中文女', '中文男', '日语男', '粤语女', '英文女', '英文男', '韩语女'] voice = forms.SingleSelect( TooltipLabel(_('timbre'), ''), required=True, default_value='中文女', text_field='value', value_field='value', option_list=[ {'text': _('Chinese female'), 'value': '中文女'}, {'text': _('Chinese male'), 'value': '中文男'}, {'text': _('Japanese male'), 'value': '日语男'}, {'text': _('Cantonese female'), 'value': '粤语女'}, {'text': _('English female'), 'value': '英文女'}, {'text': _('English male'), 'value': '英文男'}, {'text': _('Korean female'), 'value': '韩语女'}, ]) class XInferenceTTSModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True) api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_base', 'api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.check_auth() except Exception as e: if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return XInferenceTTSModelGeneralParams() ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/embedding.py ================================================ # coding=utf-8 import threading from typing import Dict, Optional, List, Any from langchain_core.embeddings import Embeddings from models_provider.base_model_provider import MaxKBBaseModel class XinferenceEmbedding(MaxKBBaseModel, Embeddings): client: Any server_url: Optional[str] """URL of the xinference server""" model_uid: Optional[str] """UID of the launched model""" @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return XinferenceEmbedding( model_uid=model_name, server_url=model_credential.get('api_base'), api_key=model_credential.get('api_key'), ) def down_model(self): self.client.launch_model(model_name=self.model_uid, model_type="embedding") def start_down_model_thread(self): thread = threading.Thread(target=self.down_model) thread.daemon = True thread.start() def __init__( self, server_url: Optional[str] = None, model_uid: Optional[str] = None, api_key: Optional[str] = None ): try: from xinference.client import RESTfulClient except ImportError: try: from xinference_client import RESTfulClient except ImportError as e: raise ImportError( "Could not import RESTfulClient from xinference. Please install it" " with `pip install xinference` or `pip install xinference_client`." ) from e if server_url is None: raise ValueError("Please provide server URL") if model_uid is None: raise ValueError("Please provide the model UID") self.server_url = server_url self.model_uid = model_uid self.api_key = api_key self.client = RESTfulClient(server_url, api_key) def embed_documents(self, texts: List[str]) -> List[List[float]]: """Embed a list of documents using Xinference. Args: texts: The list of texts to embed. Returns: List of embeddings, one for each text. """ model = self.client.get_model(self.model_uid) embeddings = [ model.create_embedding(text)["data"][0]["embedding"] for text in texts ] return [list(map(float, e)) for e in embeddings] def embed_query(self, text: str) -> List[float]: """Embed a query of documents using Xinference. Args: text: The text to embed. Returns: Embeddings for the text. """ model = self.client.get_model(self.model_uid) embedding_res = model.create_embedding(text) embedding = embedding_res["data"][0]["embedding"] return list(map(float, embedding)) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/image.py ================================================ from typing import Dict, List from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class XinferenceImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return XinferenceImage( model_name=model_name, openai_api_base=model_credential.get('api_base'), openai_api_key=model_credential.get('api_key'), # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) 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) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/llm.py ================================================ # coding=utf-8 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 models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class XinferenceChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): api_base = model_credential.get('api_base', '') base_url = get_base_url(api_base) base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1') optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return XinferenceChatModel( model=model_name, openai_api_base=base_url, openai_api_key=model_credential.get('api_key'), extra_body=optional_params ) 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) ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/reranker.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: reranker.py @date:2024/9/10 9:45 @desc: """ from typing import Sequence, Optional, Any, Dict from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document from xinference_client.client.restful.restful_client import RESTfulRerankModelHandle from models_provider.base_model_provider import MaxKBBaseModel class XInferenceReranker(MaxKBBaseModel, BaseDocumentCompressor): server_url: Optional[str] """URL of the xinference server""" model_uid: Optional[str] """UID of the launched model""" api_key: Optional[str] @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return XInferenceReranker(server_url=model_credential.get('server_url'), model_uid=model_name, api_key=model_credential.get('api_key'), top_n=model_kwargs.get('top_n', 3)) top_n: Optional[int] = 3 def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ Sequence[Document]: if documents is None or len(documents) == 0: return [] client: Any if documents is None or len(documents) == 0: return [] try: from xinference.client import RESTfulClient except ImportError: try: from xinference_client import RESTfulClient except ImportError as e: raise ImportError( "Could not import RESTfulClient from xinference. Please install it" " with `pip install xinference` or `pip install xinference_client`." ) from e client = RESTfulClient(self.server_url, self.api_key) model: RESTfulRerankModelHandle = client.get_model(self.model_uid) res = model.rerank([document.page_content for document in documents], query, self.top_n, return_documents=True) return [Document(page_content=d.get('document', {}).get('text'), metadata={'relevance_score': d.get('relevance_score')}) for d in res.get('results', [])] ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/stt.py ================================================ import io from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_stt import BaseSpeechToText def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class XInferenceSpeechToText(MaxKBBaseModel, BaseSpeechToText): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {} if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None: optional_params['max_tokens'] = model_kwargs['max_tokens'] if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None: optional_params['temperature'] = model_kwargs['temperature'] return XInferenceSpeechToText( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), params=model_kwargs, **optional_params, ) def check_auth(self): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) response_list = client.models.with_raw_response.list() # print(response_list) def speech_to_text(self, audio_file): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) audio_data = audio_file.read() buffer = io.BytesIO(audio_data) buffer.name = "file.mp3" # this is the important line filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}} transcription_params = { 'model': self.model, 'file': buffer, 'language': 'zh', **filter_params } res = client.audio.transcriptions.create(**transcription_params) return res.text ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/tti.py ================================================ import base64 from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import bytes_to_uploaded_file from knowledge.models import FileSourceType # from dataset.serializers.file_serializers import FileSerializer from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage from oss.serializers.file import FileSerializer def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class XinferenceTextToImage(MaxKBBaseModel, BaseTextToImage): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return XinferenceTextToImage( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): chat = OpenAI(api_key=self.api_key, base_url=self.api_base) res = chat.images.generate(model=self.model, prompt=prompt, response_format='b64_json', **self.params) file_urls = [] # 临时文件 for img in res.data: file_urls.append(base64.b64decode(img.b64_json)) return file_urls ================================================ FILE: apps/models_provider/impl/xinference_model_provider/model/tts.py ================================================ from typing import Dict from openai import OpenAI from common.config.tokenizer_manage_config import TokenizerManage from common.utils.common import _remove_empty_lines from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tts import BaseTextToSpeech from django.utils.translation import gettext as _ def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class XInferenceTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_base: str api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'voice': '中文女'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return XInferenceTextToSpeech( model=model_name, api_base=model_credential.get('api_base'), api_key=model_credential.get('api_key'), **optional_params, ) def check_auth(self): self.text_to_speech(_('Hello')) def text_to_speech(self, text): client = OpenAI( base_url=self.api_base, api_key=self.api_key ) # ['中文女', '中文男', '日语男', '粤语女', '英文女', '英文男', '韩语女'] text = _remove_empty_lines(text) with client.audio.speech.with_streaming_response.create( model=self.model, input=text, **self.params ) as response: return response.read() ================================================ FILE: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py ================================================ # coding=utf-8 import os from urllib.parse import urlparse, ParseResult import requests from common.utils.common import get_file_content from models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \ ModelInfoManage from models_provider.impl.xinference_model_provider.credential.embedding import \ XinferenceEmbeddingModelCredential from models_provider.impl.xinference_model_provider.credential.image import XinferenceImageModelCredential from models_provider.impl.xinference_model_provider.credential.llm import XinferenceLLMModelCredential from models_provider.impl.xinference_model_provider.credential.reranker import XInferenceRerankerModelCredential from models_provider.impl.xinference_model_provider.credential.stt import XInferenceSTTModelCredential from models_provider.impl.xinference_model_provider.credential.tti import XinferenceTextToImageModelCredential from models_provider.impl.xinference_model_provider.credential.tts import XInferenceTTSModelCredential from models_provider.impl.xinference_model_provider.model.embedding import XinferenceEmbedding from models_provider.impl.xinference_model_provider.model.image import XinferenceImage from models_provider.impl.xinference_model_provider.model.llm import XinferenceChatModel from models_provider.impl.xinference_model_provider.model.reranker import XInferenceReranker from models_provider.impl.xinference_model_provider.model.stt import XInferenceSpeechToText from models_provider.impl.xinference_model_provider.model.tti import XinferenceTextToImage from models_provider.impl.xinference_model_provider.model.tts import XInferenceTextToSpeech from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ xinference_llm_model_credential = XinferenceLLMModelCredential() xinference_stt_model_credential = XInferenceSTTModelCredential() xinference_tts_model_credential = XInferenceTTSModelCredential() xinference_image_model_credential = XinferenceImageModelCredential() xinference_tti_model_credential = XinferenceTextToImageModelCredential() model_info_list = [ ModelInfo( 'code-llama', _('Code Llama is a language model specifically designed for code generation.'), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'code-llama-instruct', _(''' Code Llama Instruct is a fine-tuned version of Code Llama's instructions, designed to perform specific tasks. '''), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'code-llama-python', _('Code Llama Python is a language model specifically designed for Python code generation.'), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'codeqwen1.5', _('CodeQwen 1.5 is a language model for code generation with high performance.'), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'codeqwen1.5-chat', _('CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5.'), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'deepseek', _('Deepseek is a large-scale language model with 13 billion parameters.'), ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'deepseek-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'deepseek-coder', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'deepseek-coder-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'deepseek-vl-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'gpt-3.5-turbo', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'gpt-4', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'gpt-4-vision-preview', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'gpt4all', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'llama2', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'llama2-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'llama2-chat-32k', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-chat-32k', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-code', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-code-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-vl', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen-vl-chat', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2-72b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2-57b-a14b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2-7b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-72b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-32b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-14b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-7b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-1.5b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-0.5b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'qwen2.5-3b-instruct', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ModelInfo( 'minicpm-llama3-v-2_5', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel ), ] voice_model_info = [ ModelInfo( 'CosyVoice-300M-SFT', '', ModelTypeConst.TTS, xinference_tts_model_credential, XInferenceTextToSpeech ), ModelInfo( 'Belle-whisper-large-v3-zh', '', ModelTypeConst.STT, xinference_stt_model_credential, XInferenceSpeechToText ), ] image_model_info = [ ModelInfo( 'qwen-vl-chat', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'deepseek-vl-chat', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'yi-vl-chat', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'omnilmm', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'internvl-chat', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'cogvlm2', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'MiniCPM-Llama3-V-2_5', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'GLM-4V', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'MiniCPM-V-2.6', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'internvl2', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'qwen2-vl-instruct', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'llama-3.2-vision', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'llama-3.2-vision-instruct', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ModelInfo( 'glm-edge-v', '', ModelTypeConst.IMAGE, xinference_image_model_credential, XinferenceImage ), ] tti_model_info = [ ModelInfo( 'sd-turbo', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'sdxl-turbo', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'stable-diffusion-v1.5', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'stable-diffusion-xl-base-1.0', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'sd3-medium', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'FLUX.1-schnell', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ModelInfo( 'FLUX.1-dev', '', ModelTypeConst.TTI, xinference_tti_model_credential, XinferenceTextToImage ), ] xinference_embedding_model_credential = XinferenceEmbeddingModelCredential() # 生成embedding_model_info列表 embedding_model_info = [ ModelInfo('bce-embedding-base_v1', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-base-en', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-base-en-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-base-zh', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-base-zh-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-large-en', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-large-en-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-large-zh', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-large-zh-noinstruct', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-large-zh-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-m3', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-small-en-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-small-zh', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('bge-small-zh-v1.5', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('e5-large-v2', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('gte-base', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('gte-large', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('jina-embeddings-v2-base-en', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('jina-embeddings-v2-base-zh', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('jina-embeddings-v2-small-en', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('m3e-base', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('m3e-large', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('m3e-small', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('multilingual-e5-large', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('text2vec-base-chinese', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('text2vec-base-chinese-paraphrase', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('text2vec-base-chinese-sentence', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('text2vec-base-multilingual', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ModelInfo('text2vec-large-chinese', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding), ] rerank_list = [ModelInfo('bce-reranker-base_v1', '', ModelTypeConst.RERANKER, XInferenceRerankerModelCredential(), XInferenceReranker)] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_model_info_list(voice_model_info) .append_default_model_info(voice_model_info[0]) .append_default_model_info(voice_model_info[1]) .append_default_model_info(ModelInfo('phi3', '', ModelTypeConst.LLM, xinference_llm_model_credential, XinferenceChatModel)) .append_model_info_list(embedding_model_info) .append_default_model_info(ModelInfo('', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential, XinferenceEmbedding)) .append_model_info_list(rerank_list) .append_model_info_list(image_model_info) .append_default_model_info(image_model_info[0]) .append_model_info_list(tti_model_info) .append_default_model_info(tti_model_info[0]) .append_default_model_info(rerank_list[0]) .build() ) def get_base_url(url: str): parse = urlparse(url) result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='', query='', fragment='').geturl() return result_url[:-1] if result_url.endswith("/") else result_url class XinferenceModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_xinference_provider', name='Xorbits Inference', icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'xinference_model_provider', 'icon', 'xinference_icon_svg'))) @staticmethod def get_base_model_list(api_base, api_key, model_type): base_url = get_base_url(api_base) base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1') headers = {} if api_key: headers['Authorization'] = f"Bearer {api_key}" r = requests.request(method="GET", url=f"{base_url}/models", headers=headers, timeout=5) r.raise_for_status() model_list = r.json().get('data') return [model for model in model_list if model.get('model_type') == model_type] @staticmethod def get_model_info_by_name(model_list, model_name): if model_list is None: return [] return [model for model in model_list if model.get('model_name') == model_name or model.get('id') == model_name] ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/credential/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/credential/image.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from common.utils.logger import maxkb_logger from models_provider.base_model_provider import BaseModelCredential, ValidCode class ZhiPuImageModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.95, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class ZhiPuImageModelCredential(BaseForm, BaseModelCredential): api_base = forms.TextInputField('API URL', required=True, default_value='https://open.bigmodel.cn/api/paas/v4') api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key', 'api_base']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.stream([HumanMessage(content=[{"type": "text", "text": gettext('Hello')}])]) for chunk in res: maxkb_logger.info(chunk) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return ZhiPuImageModelParams() ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/credential/llm.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: llm.py @date:2024/7/12 10:46 @desc: """ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from langchain_core.messages import HumanMessage from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class ZhiPuLLMModelParams(BaseForm): temperature = forms.SliderField(TooltipLabel(_('Temperature'), _('Higher values make the output more random, while lower values make it more focused and deterministic')), required=True, default_value=0.95, _min=0.1, _max=1.0, _step=0.01, precision=2) max_tokens = forms.SliderField( TooltipLabel(_('Output the maximum Tokens'), _('Specify the maximum number of tokens that the model can generate')), required=True, default_value=1024, _min=1, _max=100000, _step=1, precision=0) class ZhiPuLLMModelCredential(BaseForm, BaseModelCredential): def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) model.invoke([HumanMessage(content=gettext('Hello'))]) except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} api_base = forms.TextInputField('API URL', required=True, default_value='https://open.bigmodel.cn/api/paas/v4') api_key = forms.PasswordInputField('API Key', required=True) def get_model_params_setting_form(self, model_name): return ZhiPuLLMModelParams() ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/credential/tti.py ================================================ # coding=utf-8 from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common import forms from common.exception.app_exception import AppApiException from common.forms import BaseForm, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger class ZhiPuTTIModelParams(BaseForm): size = forms.SingleSelect( TooltipLabel(_('Image size'), _('Image size, only cogview-3-plus supports this parameter. Optional range: [1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the default is 1024x1024.')), required=True, default_value='1024x1024', option_list=[ {'value': '1024x1024', 'label': '1024x1024'}, {'value': '768x1344', 'label': '768x1344'}, {'value': '864x1152', 'label': '864x1152'}, {'value': '1344x768', 'label': '1344x768'}, {'value': '1152x864', 'label': '1152x864'}, {'value': '1440x720', 'label': '1440x720'}, {'value': '720x1440', 'label': '720x1440'}, ], text_field='label', value_field='value') class ZhiPuTextToImageModelCredential(BaseForm, BaseModelCredential): api_key = forms.PasswordInputField('API Key', required=True) def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider, raise_exception=False): model_type_list = provider.get_model_type_list() if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): raise AppApiException(ValidCode.valid_error.value, gettext('{model_type} Model type is not supported').format(model_type=model_type)) for key in ['api_key']: if key not in model_credential: if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key)) else: return False try: model = provider.get_model(model_type, model_name, model_credential, **model_params) res = model.check_auth() except Exception as e: maxkb_logger.error(f'Exception: {e}', exc_info=True) if isinstance(e, AppApiException): raise e if raise_exception: raise AppApiException(ValidCode.valid_error.value, gettext( 'Verification failed, please check whether the parameters are correct: {error}').format( error=str(e))) else: return False return True def encryption_dict(self, model: Dict[str, object]): return {**model, 'api_key': super().encryption(model.get('api_key', ''))} def get_model_params_setting_form(self, model_name): return ZhiPuTTIModelParams() ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/model/__init__.py ================================================ ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/model/image.py ================================================ from typing import Dict from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI class ZhiPuImage(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return ZhiPuImage( model_name=model_name, openai_api_key=model_credential.get('api_key'), openai_api_base=model_credential.get('api_base') or 'https://open.bigmodel.cn/api/paas/v4', # stream_options={"include_usage": True}, streaming=True, stream_usage=True, extra_body=optional_params ) ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/model/llm.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: llm.py @date:2024/4/28 11:42 @desc: """ from typing import Dict, List from langchain_core.messages import BaseMessage, get_buffer_string from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_chat_open_ai import BaseChatOpenAI def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class ZhipuChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) zhipuai_chat = ZhipuChatModel( api_key=model_credential.get('api_key'), model=model_name, base_url=model_credential.get('api_base') or 'https://open.bigmodel.cn/api/paas/v4', extra_body=optional_params, streaming=model_kwargs.get('streaming', False), custom_get_token_ids=custom_get_token_ids ) return zhipuai_chat def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int: try: return super().get_num_tokens_from_messages(messages) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages]) def get_num_tokens(self, text: str) -> int: try: return super().get_num_tokens(text) except Exception as e: tokenizer = TokenizerManage.get_tokenizer() return len(tokenizer.encode(text)) ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/model/tti.py ================================================ from typing import Dict from django.utils.translation import gettext from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from zhipuai import ZhipuAI from common.config.tokenizer_manage_config import TokenizerManage from models_provider.base_model_provider import MaxKBBaseModel from models_provider.impl.base_tti import BaseTextToImage def custom_get_token_ids(text: str): tokenizer = TokenizerManage.get_tokenizer() return tokenizer.encode(text) class ZhiPuTextToImage(MaxKBBaseModel, BaseTextToImage): api_key: str model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') self.model = kwargs.get('model') self.params = kwargs.get('params') @staticmethod def is_cache_model(): return False @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = {'params': {'size': '1024x1024'}} for key, value in model_kwargs.items(): if key not in ['model_id', 'use_local', 'streaming']: optional_params['params'][key] = value return ZhiPuTextToImage( model=model_name, api_key=model_credential.get('api_key'), **optional_params, ) def is_cache_model(self): return False def check_auth(self): chat = ChatOpenAI( api_key=self.api_key, base_url='https://open.bigmodel.cn/api/paas/v4', model=self.model, ) chat.invoke([HumanMessage([{"type": "text", "text": gettext('Hello')}])]) # self.generate_image('生成一个小猫图片') def generate_image(self, prompt: str, negative_prompt: str = None): # chat = ChatZhipuAI( # zhipuai_api_key=self.api_key, # model_name=self.model, # ) chat = ZhipuAI(api_key=self.api_key) response = chat.images.generations( model=self.model, # 填写需要调用的模型编码 prompt=prompt, # 填写需要生成图片的文本 **self.params # 填写额外参数 ) file_urls = [] try: for content in response.data: url = content.url file_urls.append(url) return file_urls except Exception as e: raise e ================================================ FILE: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: zhipu_model_provider.py @date:2024/04/19 13:5 @desc: """ import os from common.utils.common import get_file_content from models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \ ModelInfoManage from models_provider.impl.zhipu_model_provider.credential.image import ZhiPuImageModelCredential from models_provider.impl.zhipu_model_provider.credential.llm import ZhiPuLLMModelCredential from models_provider.impl.zhipu_model_provider.credential.tti import ZhiPuTextToImageModelCredential from models_provider.impl.zhipu_model_provider.model.image import ZhiPuImage from models_provider.impl.zhipu_model_provider.model.llm import ZhipuChatModel from models_provider.impl.zhipu_model_provider.model.tti import ZhiPuTextToImage from maxkb.conf import PROJECT_DIR from django.utils.translation import gettext as _ zhipu_model_credential = ZhiPuLLMModelCredential() zhipu_image_model_credential = ZhiPuImageModelCredential() zhipu_tti_model_credential = ZhiPuTextToImageModelCredential() model_info_list = [ ModelInfo('glm-4', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel), ModelInfo('glm-4v', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel), ModelInfo('glm-3-turbo', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel) ] model_info_image_list = [ ModelInfo('glm-4v-plus', _('Have strong multi-modal understanding capabilities. Able to understand up to five images simultaneously and supports video content understanding'), ModelTypeConst.IMAGE, zhipu_image_model_credential, ZhiPuImage), ModelInfo('glm-4v', _('Focus on single picture understanding. Suitable for scenarios requiring efficient image analysis'), ModelTypeConst.IMAGE, zhipu_image_model_credential, ZhiPuImage), ModelInfo('glm-4v-flash', _('Focus on single picture understanding. Suitable for scenarios requiring efficient image analysis (free)'), ModelTypeConst.IMAGE, zhipu_image_model_credential, ZhiPuImage), ] model_info_tti_list = [ ModelInfo('cogview-3', _('Quickly and accurately generate images based on user text descriptions. Resolution supports 1024x1024'), ModelTypeConst.TTI, zhipu_tti_model_credential, ZhiPuTextToImage), ModelInfo('cogview-3-plus', _('Generate high-quality images based on user text descriptions, supporting multiple image sizes'), ModelTypeConst.TTI, zhipu_tti_model_credential, ZhiPuTextToImage), ModelInfo('cogview-3-flash', _('Generate high-quality images based on user text descriptions, supporting multiple image sizes (free)'), ModelTypeConst.TTI, zhipu_tti_model_credential, ZhiPuTextToImage), ] model_info_manage = ( ModelInfoManage.builder() .append_model_info_list(model_info_list) .append_default_model_info(ModelInfo('glm-4', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel)) .append_model_info_list(model_info_image_list) .append_default_model_info(model_info_image_list[0]) .append_model_info_list(model_info_tti_list) .append_default_model_info(model_info_tti_list[0]) .build() ) class ZhiPuModelProvider(IModelProvider): def get_model_info_manage(self): return model_info_manage def get_model_provide_info(self): return ModelProvideInfo(provider='model_zhipu_provider', name=_('zhipu AI'), icon=get_file_content( os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'zhipu_model_provider', 'icon', 'zhipuai_icon_svg'))) ================================================ FILE: apps/models_provider/migrations/0001_initial.py ================================================ # Generated by Django 5.2.3 on 2025-06-23 02:14 import json import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models from common.utils.rsa_util import rsa_long_encrypt from maxkb.const import CONFIG from models_provider.models import Status default_embedding_model_id = '42f63a3d-427e-11ef-b3ec-a8a1595801ab' def save_default_embedding_model(apps, schema_editor): ModelModel = apps.get_model('models_provider', 'Model') cache_folder = CONFIG.get('EMBEDDING_MODEL_PATH') model_name = CONFIG.get('EMBEDDING_MODEL_NAME') credential = {'cache_folder': cache_folder} model_credential_str = json.dumps(credential) model = ModelModel(id=default_embedding_model_id, name='maxkb-embedding', status=Status.SUCCESS, model_type="EMBEDDING", model_name=model_name, user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', provider='model_local_provider', credential=rsa_long_encrypt(model_credential_str), meta={}, workspace_id='default') model.save() class Migration(migrations.Migration): initial = True dependencies = [ ('users', '0001_initial'), ('system_manage', '0001_initial'), ] operations = [ migrations.CreateModel( name='Model', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=128, verbose_name='名称')), ('status', models.CharField(choices=[('SUCCESS', '成功'), ('ERROR', '失败'), ('DOWNLOAD', '下载中'), ('PAUSE_DOWNLOAD', '暂停下载')], db_index=True, default='SUCCESS', max_length=20, verbose_name='设置类型')), ('model_type', models.CharField(db_index=True, max_length=128, verbose_name='模型类型')), ('model_name', models.CharField(db_index=True, max_length=128, verbose_name='模型名称')), ('provider', models.CharField(db_index=True, max_length=128, verbose_name='供应商')), ('credential', models.CharField(max_length=102400, verbose_name='模型认证信息')), ('meta', models.JSONField(default=dict, verbose_name='模型元数据,用于存储下载,或者错误信息')), ('model_params_form', models.JSONField(default=list, verbose_name='模型参数配置')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'model', 'unique_together': {('name', 'workspace_id')}, }, ), migrations.RunPython(save_default_embedding_model), ] ================================================ FILE: apps/models_provider/migrations/__init__.py ================================================ ================================================ FILE: apps/models_provider/models/__init__.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: __init__.py @date:2023/9/25 15:04 @desc: """ from .model_management import * ================================================ FILE: apps/models_provider/models/model_management.py ================================================ # coding=utf-8 import uuid_utils.compat as uuid from django.db import models from common.mixins.app_model_mixin import AppModelMixin from users.models import User class Status(models.TextChoices): """系统设置类型""" SUCCESS = "SUCCESS", '成功' ERROR = "ERROR", "失败" DOWNLOAD = "DOWNLOAD", '下载中' PAUSE_DOWNLOAD = "PAUSE_DOWNLOAD", '暂停下载' class Model(AppModelMixin): """ 模型数据 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") name = models.CharField(max_length=128, verbose_name="名称", db_index=True) status = models.CharField(max_length=20, verbose_name='设置类型', choices=Status.choices, default=Status.SUCCESS, db_index=True) model_type = models.CharField(max_length=128, verbose_name="模型类型", db_index=True) model_name = models.CharField(max_length=128, verbose_name="模型名称", db_index=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True) credential = models.CharField(max_length=102400, verbose_name="模型认证信息") meta = models.JSONField(verbose_name="模型元数据,用于存储下载,或者错误信息", default=dict) model_params_form = models.JSONField(verbose_name="模型参数配置", default=list) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) class Meta: db_table = "model" unique_together = ['name', 'workspace_id'] ================================================ FILE: apps/models_provider/serializers/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/models_provider/serializers/model_apply_serializers.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: model_apply_serializers.py @date:2024/8/20 20:39 @desc: """ from django.db import connection from django.db.models import QuerySet from langchain_core.documents import Document from rest_framework import serializers from common.config.embedding_config import ModelManage from django.utils.translation import gettext_lazy as _ from models_provider.models import Model from models_provider.tools import get_model def get_embedding_model(model_id): model = QuerySet(Model).filter(id=model_id).first() # 手动关闭数据库连接 connection.close() embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model, use_local=True)) return embedding_model class EmbedDocuments(serializers.Serializer): texts = serializers.ListField(required=True, child=serializers.CharField(required=True, label=_('vector text')), label=_('vector text list')), class EmbedQuery(serializers.Serializer): text = serializers.CharField(required=True, label=_('vector text')) class CompressDocument(serializers.Serializer): page_content = serializers.CharField(required=True, label=_('text')) metadata = serializers.DictField(required=False, label=_('metadata')) class CompressDocuments(serializers.Serializer): documents = CompressDocument(required=True, many=True) query = serializers.CharField(required=True, label=_('query')) class ModelApplySerializers(serializers.Serializer): model_id = serializers.UUIDField(required=True, label=_('model id')) def embed_documents(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EmbedDocuments(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return model.embed_documents(instance.getlist('texts')) def embed_query(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) EmbedQuery(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return model.embed_query(instance.get('text')) def compress_documents(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) CompressDocuments(data=instance).is_valid(raise_exception=True) model = get_embedding_model(self.data.get('model_id')) return [{'page_content': d.page_content, 'metadata': d.metadata} for d in model.compress_documents( [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document in instance.get('documents')], instance.get('query'))] ================================================ FILE: apps/models_provider/serializers/model_serializer.py ================================================ # -*- coding: utf-8 -*- import json import os import threading import time from typing import Dict import uuid_utils.compat as uuid from django.core.cache import cache from django.db import transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.config.embedding_config import ModelManage from common.constants.cache_version import Cache_Version from common.constants.permission_constants import ResourcePermission, ResourceAuthType from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from common.utils.rsa_util import rsa_long_encrypt, rsa_long_decrypt from maxkb.conf import PROJECT_DIR from models_provider.base_model_provider import ValidCode, DownModelChunkStatus from models_provider.constants.model_provider_constants import ModelProvideConstants from models_provider.models import Model, Status from models_provider.tools import get_model_credential from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType from system_manage.models.resource_mapping import ResourceMapping from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from users.serializers.user import is_workspace_manage def get_default_model_params_setting(provider, model_type, model_name): credential = get_model_credential(provider, model_type, model_name) setting_form = credential.get_model_params_setting_form(model_name) if setting_form is not None: return setting_form.to_form_list() return [] class ModelModelSerializer(serializers.ModelSerializer): class Meta: model = Model fields = [ 'id', 'name', 'status', 'model_type', 'model_name', 'user', 'provider', 'credential', 'meta', 'model_params_form', 'workspace_id', 'create_time', 'update_time' ] class ModelCreateRequest(serializers.Serializer): name = serializers.CharField(required=True, max_length=64, label=_("model name")) provider = serializers.CharField(required=True, label=_("provider")) model_type = serializers.CharField(required=True, label=_("model type")) model_name = serializers.CharField(required=True, label=_("base model")) model_params_form = serializers.ListField(required=False, default=list, label=_("parameter configuration")) credential = serializers.DictField(required=True, label=_("certification information")) class ModelPullManage: @staticmethod def pull(model: Model, credential: Dict): try: response = ModelProvideConstants[model.provider].value.down_model( model.model_type, model.model_name, credential ) down_model_chunk = {} last_update_time = time.time() for chunk in response: down_model_chunk[chunk.digest] = chunk.to_dict() if time.time() - last_update_time > 5: current_model = QuerySet(Model).filter(id=model.id).first() if current_model and current_model.status == Status.PAUSE_DOWNLOAD: return QuerySet(Model).filter(id=model.id).update( meta={"down_model_chunk": list(down_model_chunk.values())} ) last_update_time = time.time() status = Status.ERROR message = "" for chunk in down_model_chunk.values(): if chunk.get('status') == DownModelChunkStatus.success.value: status = Status.SUCCESS elif chunk.get('status') == DownModelChunkStatus.error.value: message = chunk.get("digest") QuerySet(Model).filter(id=model.id).update( meta={"down_model_chunk": [], "message": message}, status=status ) except Exception as e: QuerySet(Model).filter(id=model.id).update( meta={"down_model_chunk": [], "message": str(e)}, status=Status.ERROR ) class ModelSerializer(serializers.Serializer): @staticmethod def model_to_dict(model: Model): credential = json.loads(rsa_long_decrypt(model.credential)) return { 'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type, 'model_name': model.model_name, 'status': model.status, 'meta': model.meta, 'credential': ModelProvideConstants[model.provider].value.get_model_credential( model.model_type, model.model_name ).encryption_dict(credential), 'workspace_id': model.workspace_id, 'nick_name': model.user.nick_name if model.user else '', 'username': model.user.username if model.user else '' } class Operate(serializers.Serializer): id = serializers.UUIDField(required=True, label=_("model id")) user_id = serializers.UUIDField(required=False, label=_("user id")) workspace_id = serializers.CharField(required=False, label=_("workspace id")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") model_query = QuerySet(Model).filter(id=self.data.get("id")) if workspace_id is not None: model_query = model_query.filter(workspace_id=workspace_id) model = model_query.first() if model is None: raise AppApiException(500, _('Model does not exist')) if model.workspace_id == 'None': raise AppApiException(500, _('Shared models cannot be deleted or modified')) def one(self, with_valid=False): if with_valid: super().is_valid(raise_exception=True) model = QuerySet(Model).get( id=self.data.get('id'), workspace_id=self.data.get('workspace_id', 'None') ) return ModelSerializer.model_to_dict(model) def one_meta(self, with_valid=False): model = None if with_valid: super().is_valid(raise_exception=True) model = QuerySet(Model).filter(id=self.data.get("id"), workspace_id=self.data.get('workspace_id', 'None')).first() if model is None: raise AppApiException(500, _('Model does not exist')) return {'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type, 'model_name': model.model_name, 'status': model.status, 'meta': model.meta, 'workspace_id': model.workspace_id, } def pause_download(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) QuerySet(Model).filter(id=self.data.get('id')).update(status=Status.PAUSE_DOWNLOAD) return True @transaction.atomic def delete(self, with_valid=True): if with_valid: super().is_valid(raise_exception=True) model_id = self.data.get('id') model = Model.objects.filter(id=model_id).first() if model is None: return True QuerySet(WorkspaceUserResourcePermission).filter(target=model_id).delete() # TODO : 这里可以添加模型删除的逻辑,需要注意删除模型时的权限和关联关系 # if model.model_type == 'LLM': # application_count = Application.objects.filter(model_id=model_id).count() # if application_count > 0: # raise AppApiException(500, f"该模型关联了{application_count} 个应用,无法删除该模型。") # elif model.model_type == 'EMBEDDING': # dataset_count = DataSet.objects.filter(embedding_model_id=model_id).count() # if dataset_count > 0: # raise AppApiException(500, f"该模型关联了{dataset_count} 个知识库,无法删除该模型。") # elif model.model_type == 'TTS': # dataset_count = Application.objects.filter(tts_model_id=model_id).count() # if dataset_count > 0: # raise AppApiException(500, f"该模型关联了{dataset_count} 个应用,无法删除该模型。") # elif model.model_type == 'STT': # dataset_count = Application.objects.filter(stt_model_id=model_id).count() # if dataset_count > 0: # raise AppApiException(500, f"该模型关联了{dataset_count} 个应用,无法删除该模型。") model.delete() ResourceMapping.objects.filter(target_id=model_id).delete() return True def edit(self, instance: Dict, user_id: str, with_valid=True): if with_valid: super().is_valid(raise_exception=True) model = QuerySet(Model).filter(id=self.data.get('id')).first() credential, model_credential, provider_handler = ModelSerializer.Edit( data={**instance}).is_valid( model=model) try: model.status = Status.SUCCESS default_params = {item['field']: item['default_value'] for item in model.model_params_form} # 校验模型认证数据 provider_handler.is_valid_credential(model.model_type, instance.get("model_name"), credential, default_params, raise_exception=True) except AppApiException as e: if e.code == ValidCode.model_not_fount: model.status = Status.DOWNLOAD else: raise e update_keys = ['credential', 'name', 'model_type', 'model_name'] for update_key in update_keys: if update_key in instance and instance.get(update_key) is not None: if update_key == 'credential': model_credential_str = json.dumps(credential) model.__setattr__(update_key, rsa_long_encrypt(model_credential_str)) else: model.__setattr__(update_key, instance.get(update_key)) ModelManage.delete_key(str(model.id)) model.save() if model.status == Status.DOWNLOAD: thread = threading.Thread(target=ModelPullManage.pull, args=(model, credential)) thread.start() return self.one(with_valid=False) class Edit(serializers.Serializer): user_id = serializers.CharField(required=False, label=(_('user id'))) name = serializers.CharField(required=False, max_length=64, label=(_("model name"))) model_type = serializers.CharField(required=False, label=(_("model type"))) model_name = serializers.CharField(required=False, label=(_("base model"))) credential = serializers.DictField(required=False, label=(_("certification information"))) workspace_id = serializers.CharField(required=False, label=(_("workspace id"))) def is_valid(self, model=None, raise_exception=False): super().is_valid(raise_exception=True) filter_params = {'workspace_id': model.workspace_id} if 'name' in self.data and self.data.get('name') is not None: filter_params['name'] = self.data.get('name') if QuerySet(Model).exclude(id=model.id).filter(**filter_params).exists(): raise AppApiException(500, _('base model【{model_name}】already exists').format( model_name=self.data.get("name"))) ModelSerializer.model_to_dict(model) provider = model.provider model_type = self.data.get('model_type') model_name = self.data.get( 'model_name') credential = self.data.get('credential') provider_handler = ModelProvideConstants[provider].value model_credential = ModelProvideConstants[provider].value.get_model_credential(model_type, model_name) source_model_credential = json.loads(rsa_long_decrypt(model.credential)) source_encryption_model_credential = model_credential.encryption_dict(source_model_credential) if credential is not None: for k in source_encryption_model_credential.keys(): if k in credential and credential[k] == source_encryption_model_credential[k]: credential[k] = source_model_credential[k] return credential, model_credential, provider_handler class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) name = serializers.CharField(required=True, max_length=64, label=_("model name")) provider = serializers.CharField(required=True, label=_("provider")) model_type = serializers.CharField(required=True, label=_("model type")) model_name = serializers.CharField(required=True, label=_("base model")) model_params_form = serializers.ListField(required=False, default=list, label=_("parameter configuration")) credential = serializers.DictField(required=True, label=_("certification information")) workspace_id = serializers.CharField(required=False, label=_("workspace id"), max_length=128) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if QuerySet(Model).filter( name=self.data.get('name'), workspace_id=self.data.get('workspace_id', 'None') ).exists(): raise AppApiException( 500, _('base model【{model_name}】already exists').format(model_name=self.data.get("name")) ) default_params = {item['field']: item['default_value'] for item in self.data.get('model_params_form')} ModelProvideConstants[self.data.get('provider')].value.is_valid_credential( self.data.get('model_type'), self.data.get('model_name'), self.data.get('credential'), default_params, raise_exception=True ) def insert(self, workspace_id, with_valid=True): status = Status.SUCCESS if with_valid: try: self.is_valid(raise_exception=True) except AppApiException as e: if e.code == ValidCode.model_not_fount: status = Status.DOWNLOAD else: raise e credential = self.data.get('credential') model_data = { 'id': uuid.uuid7(), 'status': status, 'user_id': self.data.get('user_id'), 'name': self.data.get('name'), 'credential': rsa_long_encrypt(json.dumps(credential)), 'provider': self.data.get('provider'), 'model_type': self.data.get('model_type'), 'model_name': self.data.get('model_name'), 'model_params_form': self.data.get('model_params_form'), 'workspace_id': workspace_id } model = Model(**model_data) try: model.save() if workspace_id != 'None': UserResourcePermissionSerializer(data={ 'workspace_id': workspace_id, 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.MODEL.value }).auth_resource(str(model.id)) except Exception as save_error: # 可添加日志记录 raise AppApiException(500, _("Model saving failed")) from save_error if status == Status.DOWNLOAD: thread = threading.Thread(target=ModelPullManage.pull, args=(model, credential)) thread.start() return ModelModelSerializer(model).data class Query(serializers.Serializer): user_id = serializers.CharField(required=True, label=_("User ID")) name = serializers.CharField(required=False, max_length=64, label=_('model name')) model_type = serializers.CharField(required=False, label=_('model type')) model_name = serializers.CharField(required=False, label=_('base model')) provider = serializers.CharField(required=False, label=_('provider')) create_user = serializers.CharField(required=False, label=_('create user')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def list(self, workspace_id, with_valid): if with_valid: self.is_valid(raise_exception=True) user_id = self.data.get("user_id") workspace_manage = is_workspace_manage(user_id, workspace_id) query_params = self._build_query_params(workspace_id, workspace_manage, user_id) is_x_pack_ee = self.is_x_pack_ee() result = native_search(query_params, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "models_provider", 'sql', 'list_model.sql' if workspace_manage else ( 'list_model_user_ee.sql' if is_x_pack_ee else 'list_model_user.sql') ))) return ResourceMappingSerializer().get_resource_count(result) def share_list(self, workspace_id, with_valid=True): if with_valid: self.is_valid(raise_exception=True) user_id = self.data.get("user_id") query_params = self._build_query_params(workspace_id, False, user_id) result = [ self._build_model_data( model ) for model in query_params.get('model_query_set') ] return ResourceMappingSerializer().get_resource_count(result) def model_list(self, workspace_id, with_valid=True): if with_valid: self.is_valid(raise_exception=True) user_id = self.data.get("user_id") workspace_manage = is_workspace_manage(user_id, workspace_id) queryset = self._build_query_params(workspace_id, workspace_manage, user_id) get_authorized_model = DatabaseModelManage.get_model("get_authorized_model") shared_queryset = QuerySet(Model).none() if get_authorized_model is not None: shared_queryset = self._build_query_params('None', False, user_id)['model_query_set'] shared_queryset = get_authorized_model(shared_queryset, workspace_id) # 构建共享模型和普通模型列表 shared_model = [self._build_model_data(model) for model in shared_queryset] is_x_pack_ee = self.is_x_pack_ee() normal_model = native_search( queryset, select_string=get_file_content( os.path.join( PROJECT_DIR, "apps", "models_provider", 'sql', 'list_model.sql' if workspace_manage else ( 'list_model_user_ee.sql' if is_x_pack_ee else 'list_model_user.sql') ) ) ) return { "shared_model": shared_model, "model": normal_model } def _build_query_params(self, workspace_id, workspace_manage: bool, user_id): queryset = QuerySet(Model) if workspace_id: queryset = queryset.filter(workspace_id=workspace_id) for field in ['name', 'model_type', 'model_name', 'provider', 'create_user']: value = self.data.get(field) if value is not None: if field == 'name': queryset = queryset.filter(**{f'{field}__icontains': value}) elif field == 'create_user': queryset = queryset.filter(user_id=value) else: queryset = queryset.filter(**{field: value}) queryset = queryset.order_by("-create_time") return { 'model_query_set': queryset, 'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter( auth_target_type="MODEL", workspace_id=workspace_id, user_id=user_id)} if ( not workspace_manage) else { 'model_query_set': queryset, } def _build_model_data(self, model): return { 'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type, 'model_name': model.model_name, 'status': model.status, 'meta': model.meta, 'user_id': model.user_id, 'username': model.user.username, 'nick_name': model.user.nick_name, } def page(self, current_page, page_size): pass class ModelParams(serializers.Serializer): id = serializers.UUIDField(required=True, label=_('model id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) model = QuerySet(Model).filter(id=self.data.get("id")).first() if model is None: raise AppApiException(500, _("Model does not exist")) def get_model_params(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) model_id = self.data.get('id') model = QuerySet(Model).filter(id=model_id).first() return model.model_params_form def save_model_params_form(self, model_params_form, with_valid=True): if with_valid: self.is_valid(raise_exception=True) if model_params_form is None: model_params_form = [] model_id = self.data.get('id') model = QuerySet(Model).filter(id=model_id).first() model.model_params_form = model_params_form model.save() return True class WorkspaceSharedModelSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) name = serializers.CharField(required=False, max_length=64, label=_('model name')) model_type = serializers.CharField(required=False, label=_('model type')) model_name = serializers.CharField(required=False, label=_('base model')) provider = serializers.CharField(required=False, label=_('provider')) create_user = serializers.CharField(required=False, label=_('create user')) def get_share_model_list(self): self.is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') queryset = self._build_queryset(workspace_id) return [ { 'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type, 'model_name': model.model_name, 'status': model.status, 'meta': model.meta, 'user_id': model.user_id, 'nick_name': model.user.nick_name, 'username': model.user.username } for model in queryset.order_by("-create_time") ] def _build_queryset(self, workspace_id): queryset = QuerySet(Model) if workspace_id: get_authorized_model = DatabaseModelManage.get_model("get_authorized_model") if get_authorized_model is not None: queryset = get_authorized_model(queryset, workspace_id) for field in ['name', 'model_type', 'model_name', 'provider', 'create_user']: value = self.data.get(field) if value is not None: if field == 'name': queryset = queryset.filter(**{f'{field}__icontains': value}) elif field == 'create_user': queryset = queryset.filter(user_id=value) else: queryset = queryset.filter(**{field: value}) return queryset ================================================ FILE: apps/models_provider/sql/list_model.sql ================================================ SELECT model."id"::text, model."name", model.model_name, model.meta::json as meta, model.credential, model.model_params_form, model.model_type, model.provider, model.status, model.create_time, model.update_time, model.user_id, "user"."nick_name" as "nick_name", model.workspace_id from model left join "user" on user_id = "user".id ${model_query_set} ================================================ FILE: apps/models_provider/sql/list_model_user.sql ================================================ SELECT * FROM (SELECT model."id"::text, model."name", model.model_name, model.meta::json as meta, model.credential, model.model_params_form, model.model_type, model.provider, model.status, model.create_time, model.update_time, model.user_id, "user"."nick_name" as "nick_name", model.workspace_id from model left join "user" on user_id = "user".id where model."id"::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and 'VIEW' = any (permission_list)) ) temp ${model_query_set} ================================================ FILE: apps/models_provider/sql/list_model_user_ee.sql ================================================ SELECT * FROM (SELECT model."id"::text, model."name", model.model_name, model.meta::json as meta, model.credential, model.model_params_form, model.model_type, model.provider, model.status, model.create_time, model.update_time, model.user_id, "user"."nick_name" as "nick_name", model.workspace_id from model left join "user" on user_id = "user".id where model."id"::text in (select target from workspace_user_resource_permission ${workspace_user_resource_permission_query_set} and case when auth_type = 'ROLE' then 'ROLE' = any (permission_list) and 'MODEL:READ' in (select (case when user_role_relation.role_id = any (array['USER']) THEN 'MODEL:READ' else role_permission.permission_id END) from role_permission role_permission right join user_role_relation user_role_relation on user_role_relation.role_id = role_permission.role_id where user_role_relation.user_id = workspace_user_resource_permission.user_id and user_role_relation.workspace_id = workspace_user_resource_permission.workspace_id) else 'VIEW' = any (permission_list) end) ) temp ${model_query_set} ================================================ FILE: apps/models_provider/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/models_provider/tools.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: tools.py @date:2024/7/22 11:18 @desc: """ from django.db import connection from django.db.models import QuerySet from common.config.embedding_config import ModelManage from common.database_model_manage.database_model_manage import DatabaseModelManage from models_provider.models import Model from django.utils.translation import gettext_lazy as _ import json from typing import Dict from common.utils.rsa_util import rsa_long_decrypt from models_provider.constants.model_provider_constants import ModelProvideConstants def get_model_(provider, model_type, model_name, credential, model_id, use_local=False, **kwargs): """ 获取模型实例 @param provider: 供应商 @param model_type: 模型类型 @param model_name: 模型名称 @param credential: 认证信息 @param model_id: 模型id @param use_local: 是否调用本地模型 只适用于本地供应商 @return: 模型实例 """ model = get_provider(provider).get_model(model_type, model_name, json.loads( rsa_long_decrypt(credential)), model_id=model_id, use_local=use_local, streaming=True, **kwargs) return model def get_model(model, **kwargs): """ 获取模型实例 @param model: model 数据库Model实例对象 @return: 模型实例 """ return get_model_(model.provider, model.model_type, model.model_name, model.credential, str(model.id), **kwargs) def get_provider(provider): """ 获取供应商实例 @param provider: 供应商字符串 @return: 供应商实例 """ return ModelProvideConstants[provider].value def get_model_list(provider, model_type): """ 获取模型列表 @param provider: 供应商字符串 @param model_type: 模型类型 @return: 模型列表 """ return get_provider(provider).get_model_list(model_type) def get_model_credential(provider, model_type, model_name): """ 获取模型认证实例 @param provider: 供应商字符串 @param model_type: 模型类型 @param model_name: 模型名称 @return: 认证实例对象 """ return get_provider(provider).get_model_credential(model_type, model_name) def get_model_type_list(provider): """ 获取模型类型列表 @param provider: 供应商字符串 @return: 模型类型列表 """ return get_provider(provider).get_model_type_list() def is_valid_credential(provider, model_type, model_name, model_credential: Dict[str, object], model_params, raise_exception=False): """ 校验模型认证参数 @param provider: 供应商字符串 @param model_type: 模型类型 @param model_name: 模型名称 @param model_credential: 模型认证数据 @param raise_exception: 是否抛出错误 @return: True|False """ return get_provider(provider).is_valid_credential(model_type, model_name, model_credential, model_params, raise_exception) def get_model_by_id(_id, workspace_id): model = QuerySet(Model).filter(id=_id).first() # 归还链接到连接池 connection.close() get_authorized_model = DatabaseModelManage.get_model("get_authorized_model") if model and model.workspace_id != workspace_id and get_authorized_model is not None: model = get_authorized_model(QuerySet(Model).filter(id=_id), workspace_id).first() if model is None: raise Exception(_("Model does not exist")) return model def get_model_default_params(model): def convert_to_int(value): if isinstance(value, str): try: return int(value) except ValueError: return value return value return { p.get('field'): convert_to_int(p.get('default_value')) for p in model.model_params_form if p.get('default_value') is not None } def get_model_instance_by_model_workspace_id(model_id, workspace_id, **kwargs): """ 获取模型实例,根据模型相关数据 @param model_id: 模型id @param workspace_id: 工作空间id @return: 模型实例 """ model = get_model_by_id(model_id, workspace_id) s = get_model_default_params(model) return ModelManage.get_model(model_id, lambda _id: get_model(model, **{**s, **kwargs})) ================================================ FILE: apps/models_provider/urls.py ================================================ import os from django.urls import path from . import views app_name = "models_provider" # @formatter:off urlpatterns = [ path('provider', views.Provide.as_view()), path('provider/model_type_list', views.Provide.ModelTypeList.as_view()), path('provider/model_list', views.Provide.ModelList.as_view()), path('provider/model_params_form', views.Provide.ModelParamsForm.as_view()), path('provider/model_form', views.Provide.ModelForm.as_view()), path('workspace//model', views.ModelSetting.as_view()), path('workspace//model_list', views.ModelList.as_view()), path('workspace//model//model_params_form', views.ModelSetting.ModelParamsForm.as_view()), path('workspace//model/', views.ModelSetting.Operate.as_view()), path('workspace//model//pause_download', views.ModelSetting.PauseDownload.as_view()), path('workspace//model//meta', views.ModelSetting.ModelMeta.as_view()), path('system/shared/workspace//model', views.WorkspaceSharedModelSetting.as_view()), ] if os.environ.get('SERVER_NAME', 'web') == 'local_model': urlpatterns += [ path('model//embed_documents', views.ModelApply.EmbedDocuments.as_view()), path('model//embed_query', views.ModelApply.EmbedQuery.as_view()), path('model//compress_documents', views.ModelApply.CompressDocuments.as_view()), ] ================================================ FILE: apps/models_provider/views/__init__.py ================================================ # coding=utf-8 from .model import * from .provide import * from .model_apply import * ================================================ FILE: apps/models_provider/views/model.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 19:25 @desc: """ from django.db.models import QuerySet from drf_spectacular.utils import extend_schema from rest_framework.views import APIView from django.utils.translation import gettext_lazy as _ from rest_framework.request import Request from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from common.utils.common import query_params_to_single_dict from models_provider.api.model import ModelCreateAPI, GetModelApi, ModelEditApi, ModelListResponse, DefaultModelResponse from models_provider.api.provide import ProvideApi from models_provider.models import Model from models_provider.serializers.model_serializer import ModelSerializer, \ WorkspaceSharedModelSerializer from system_manage.views import encryption_str def encryption_credential(credential): if isinstance(credential, dict): return {key: encryption_str(credential.get(key)) for key in credential} return credential def get_edit_model_details(request): path = request.path body = request.data query = request.query_params credential = body.get('credential', {}) credential_encryption_ed = encryption_credential(credential) return { 'path': path, 'body': {**body, 'credential': credential_encryption_ed}, 'query': query } def get_model_operation_object(model_id): model_model = QuerySet(model=Model).filter(id=model_id).first() if model_model is not None: return { "name": model_model.name } return {} class ModelSetting(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Create model"), description=_("Create model"), operation_id=_("Create model"), # type: ignore tags=[_("Model")], # type: ignore parameters=ModelCreateAPI.get_parameters(), request=ModelCreateAPI.get_request(), responses=ModelCreateAPI.get_response()) @has_permissions(PermissionConstants.MODEL_CREATE.get_workspace_permission(), PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()) @log(menu='model', operate='Create model', get_operation_object=lambda r, k: {'name': r.date.get('name')}, get_details=get_edit_model_details, ) def post(self, request: Request, workspace_id: str): return result.success( ModelSerializer.Create( data={**request.data, 'user_id': request.user.id, 'workspace_id': workspace_id}).insert(workspace_id, with_valid=True)) # @extend_schema(methods=['PUT'], # summary=_('Update model'), # operation_id=_('Update model'), # type: ignore # request=ModelEditApi.get_request(), # responses=ModelCreateApi.get_response(), # tags=[_('Model')]) # type: ignore # @has_permissions(PermissionConstants.MODEL_CREATE) # def put(self, request: Request): # return result.success( # ModelSerializer.Create(data={**request.data, 'user_id': str(request.user.id)}).insert(request.user.id, # with_valid=True)) @extend_schema(methods=['GET'], summary=_('Query model list'), description=_('Query model list'), operation_id=_('Query model list'), # type: ignore parameters=ModelListResponse.get_parameters(), responses=ModelListResponse.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_READ.get_workspace_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()) def get(self, request: Request, workspace_id: str): return result.success( ModelSerializer.Query( data={**query_params_to_single_dict(request.query_params), 'user_id': str(request.user.id)}).list( workspace_id=workspace_id, with_valid=True)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['PUT'], summary=_('Update model'), description=_('Update model'), operation_id=_('Update model'), # type: ignore request=ModelEditApi.get_request(), parameters=GetModelApi.get_parameters(), responses=ModelEditApi.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_EDIT.get_workspace_model_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) @log(menu='model', operate='Update model', get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')), get_details=get_edit_model_details, ) def put(self, request: Request, workspace_id, model_id: str): return result.success( ModelSerializer.Operate( data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).edit(request.data, str(request.user.id))) @extend_schema(methods=['DELETE'], summary=_('Delete model'), description=_('Delete model'), operation_id=_('Delete model'), # type: ignore parameters=GetModelApi.get_parameters(), responses=DefaultModelResponse.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_DELETE.get_workspace_model_permission(), PermissionConstants.MODEL_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) @log(menu='model', operate='Delete model', get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')), ) def delete(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.Operate( data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).delete()) @extend_schema(methods=['GET'], summary=_('Query model details'), description=_('Query model details'), operation_id=_('Query model details'), # type: ignore parameters=GetModelApi.get_parameters(), responses=GetModelApi.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.Operate( data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).one( with_valid=True)) class ModelParamsForm(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Get model parameter form'), description=_('Get model parameter form'), operation_id=_('Get model parameter form'), # type: ignore parameters=GetModelApi.get_parameters(), responses=ProvideApi.ModelParamsForm.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(), PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.MODEL_READ.get_workspace_permission(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(),) def get(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.ModelParams(data={'id': model_id}).get_model_params()) @extend_schema(methods=['PUT'], summary=_('Save model parameter form'), description=_('Save model parameter form'), operation_id=_('Save model parameter form'), # type: ignore parameters=GetModelApi.get_parameters(), request=GetModelApi.get_request(), responses=ProvideApi.ModelParamsForm.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_EDIT.get_workspace_model_permission(), PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), PermissionConstants.MODEL_READ.get_workspace_permission(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) @log(menu='model', operate='Save model parameter form', get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')), ) def put(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.ModelParams(data={'id': model_id}).save_model_params_form(request.data)) class ModelMeta(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_( 'Query model meta information, this interface does not carry authentication information'), description=_( 'Query model meta information, this interface does not carry authentication information'), operation_id=_( 'Query model meta information, this interface does not carry authentication information'), parameters=GetModelApi.get_parameters(), responses=GetModelApi.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), PermissionConstants.MODEL_READ.get_workspace_permission(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) def get(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.Operate(data={'id': model_id, 'workspace_id': workspace_id}).one_meta(with_valid=True)) class PauseDownload(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['PUT'], summary=_('Pause model download'), description=_('Pause model download'), operation_id=_('Pause model download'), # type: ignore parameters=GetModelApi.get_parameters(), request=GetModelApi.get_request(), responses=DefaultModelResponse.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_CREATE.get_workspace_model_permission(), PermissionConstants.MODEL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.MODEL.get_workspace_model_permission()], CompareConstants.AND), ) def put(self, request: Request, workspace_id: str, model_id: str): return result.success( ModelSerializer.Operate(data={'id': model_id, 'workspace_id': workspace_id}).pause_download()) class WorkspaceSharedModelSetting(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['Get'], summary=_('Get Share model by workspace id'), description=_('Get Share model by workspace id'), operation_id=_('Get Share model by workspace id'), # type: ignore parameters=ModelListResponse.get_parameters(), responses=DefaultModelResponse.get_response(), tags=[_('Shared Model')] ) # type: ignore @has_permissions( PermissionConstants.MODEL_READ.get_workspace_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), ) def get(self, request: Request, workspace_id: str): return result.success( WorkspaceSharedModelSerializer(data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id}).get_share_model_list()) class ModelList(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Query all model list'), description=_('Query all model list'), operation_id=_('Query all model list'), # type: ignore parameters=ModelListResponse.get_parameters(), responses=ModelListResponse.get_response(), tags=[_('Model')]) # type: ignore @has_permissions(PermissionConstants.MODEL_READ.get_workspace_permission(), PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission(), PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()) def get(self, request: Request, workspace_id: str): return result.success( ModelSerializer.Query( data={**query_params_to_single_dict(request.query_params), 'user_id': str(request.user.id)}).model_list( workspace_id=workspace_id, with_valid=True)) ================================================ FILE: apps/models_provider/views/model_apply.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: model_apply.py @date:2024/8/20 20:38 @desc: """ from urllib.request import Request from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.views import APIView from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants from common.result import result from models_provider.api.model import DefaultModelResponse from models_provider.serializers.model_apply_serializers import ModelApplySerializers class ModelApply(APIView): class EmbedDocuments(APIView): @extend_schema(methods=['POST'], summary=_('Vectorization documentation'), description=_('Vectorization documentation'), operation_id=_('Vectorization documentation'), # type: ignore responses=DefaultModelResponse.get_response(), tags=[_('Model')] # type: ignore ) def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).embed_documents(request.data)) class EmbedQuery(APIView): @extend_schema(methods=['POST'], summary=_('Vectorization documentation'), description=_('Vectorization documentation'), operation_id=_('Vectorization documentation'), # type: ignore responses=DefaultModelResponse.get_response(), tags=[_('Model')] # type: ignore ) def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).embed_query(request.data)) class CompressDocuments(APIView): @extend_schema(methods=['POST'], summary=_('Reorder documents'), description=_('Reorder documents'), operation_id=_('Reorder documents'), # type: ignore responses=DefaultModelResponse.get_response(), tags=[_('Model')] # type: ignore ) def post(self, request: Request, model_id): return result.success( ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data)) ================================================ FILE: apps/models_provider/views/provide.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants from models_provider.api.provide import ProvideApi from models_provider.constants.model_provider_constants import ModelProvideConstants from models_provider.serializers.model_serializer import get_default_model_params_setting class Provide(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Get a list of model suppliers'), description=_('Get a list of model suppliers'), operation_id=_('Get a list of model suppliers'), # type: ignore responses=ProvideApi.get_response(), tags=[_('Model')]) # type: ignore def get(self, request: Request): model_type = request.query_params.get('model_type') if model_type: providers = [] for key in ModelProvideConstants.__members__: if len([item for item in ModelProvideConstants[key].value.get_model_type_list() if item['value'] == model_type]) > 0: providers.append(ModelProvideConstants[key].value.get_model_provide_info().to_dict()) return result.success(providers) return result.success( [ModelProvideConstants[key].value.get_model_provide_info().to_dict() for key in ModelProvideConstants.__members__]) class ModelTypeList(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Get a list of model types'), description=_('Get a list of model types'), operation_id=_('Get a list of model types'), # type: ignore parameters=ProvideApi.ModelTypeList.get_query_params_api(), responses=ProvideApi.ModelTypeList.get_response(), tags=[_('Model')]) # type: ignore def get(self, request: Request): provider = request.query_params.get('provider') return result.success(ModelProvideConstants[provider].value.get_model_type_list()) class ModelList(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Example of obtaining model list'), description=_('Example of obtaining model list'), operation_id=_('Example of obtaining model list'), # type: ignore parameters=ProvideApi.ModelList.get_query_params_api(), responses=ProvideApi.ModelList.get_response(), tags=[_('Model')]) # type: ignore def get(self, request: Request): provider = request.query_params.get('provider') model_type = request.query_params.get('model_type') return result.success( ModelProvideConstants[provider].value.get_model_list( model_type)) class ModelParamsForm(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Get model default parameters'), description=_('Get model default parameters'), operation_id=_('Get model default parameters'), # type: ignore parameters=ProvideApi.ModelParamsForm.get_query_params_api(), responses=ProvideApi.ModelParamsForm.get_response(), tags=[_('Model')]) # type: ignore def get(self, request: Request): provider = request.query_params.get('provider') model_type = request.query_params.get('model_type') model_name = request.query_params.get('model_name') return result.success(get_default_model_params_setting(provider, model_type, model_name)) class ModelForm(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_('Get the model creation form'), description=_('Get the model creation form'), operation_id=_('Get the model creation form'), # type: ignore parameters=ProvideApi.ModelParamsForm.get_query_params_api(), responses=ProvideApi.ModelParamsForm.get_response(), tags=[_('Model')]) # type: ignore def get(self, request: Request): provider = request.query_params.get('provider') model_type = request.query_params.get('model_type') model_name = request.query_params.get('model_name') return result.success( ModelProvideConstants[provider].value.get_model_credential(model_type, model_name).to_form_list()) ================================================ FILE: apps/ops/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: __init__.py.py @date:2024/8/16 14:47 @desc: """ from .celery import app as celery_app ================================================ FILE: apps/ops/celery/__init__.py ================================================ # -*- coding: utf-8 -*- import os from celery import Celery from kombu import Exchange, Queue from maxkb import settings from .heartbeat import * from .hmac_signed_serializer import register_hmac_signed_serializer # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings') register_hmac_signed_serializer() app = Celery('MaxKB') configs = {k: v for k, v in settings.__dict__.items() if k.startswith('CELERY')} configs['worker_concurrency'] = 5 # Using a string here means the worker will not have to # pickle the object when using Windows. # app.config_from_object('django.conf:settings', namespace='CELERY') configs["task_queues"] = [ Queue("celery", Exchange("celery"), routing_key="celery"), Queue("model", Exchange("model"), routing_key="model") ] app.namespace = 'CELERY' app.conf.update( {key.replace('CELERY_', '') if key.replace('CELERY_', '').lower() == key.replace('CELERY_', '') else key: configs.get( key) for key in configs.keys()}) app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS]) ================================================ FILE: apps/ops/celery/const.py ================================================ # -*- coding: utf-8 -*- # CELERY_LOG_MAGIC_MARK = b'\x00\x00\x00\x00\x00' ================================================ FILE: apps/ops/celery/decorator.py ================================================ # -*- coding: utf-8 -*- # from functools import wraps _need_registered_period_tasks = [] _after_app_ready_start_tasks = [] _after_app_shutdown_clean_periodic_tasks = [] def add_register_period_task(task): _need_registered_period_tasks.append(task) def get_register_period_tasks(): return _need_registered_period_tasks def add_after_app_shutdown_clean_task(name): _after_app_shutdown_clean_periodic_tasks.append(name) def get_after_app_shutdown_clean_tasks(): return _after_app_shutdown_clean_periodic_tasks def add_after_app_ready_task(name): _after_app_ready_start_tasks.append(name) def get_after_app_ready_tasks(): return _after_app_ready_start_tasks def register_as_period_task( crontab=None, interval=None, name=None, args=(), kwargs=None, description=''): """ Warning: Task must have not any args and kwargs :param crontab: "* * * * *" :param interval: 60*60*60 :param args: () :param kwargs: {} :param description: " :param name: "" :return: """ if crontab is None and interval is None: raise SyntaxError("Must set crontab or interval one") def decorate(func): if crontab is None and interval is None: raise SyntaxError("Interval and crontab must set one") # Because when this decorator run, the task was not created, # So we can't use func.name task = '{func.__module__}.{func.__name__}'.format(func=func) _name = name if name else task add_register_period_task({ _name: { 'task': task, 'interval': interval, 'crontab': crontab, 'args': args, 'kwargs': kwargs if kwargs else {}, 'description': description } }) @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorate def after_app_ready_start(func): # Because when this decorator run, the task was not created, # So we can't use func.name name = '{func.__module__}.{func.__name__}'.format(func=func) if name not in _after_app_ready_start_tasks: add_after_app_ready_task(name) @wraps(func) def decorate(*args, **kwargs): return func(*args, **kwargs) return decorate def after_app_shutdown_clean_periodic(func): # Because when this decorator run, the task was not created, # So we can't use func.name name = '{func.__module__}.{func.__name__}'.format(func=func) if name not in _after_app_shutdown_clean_periodic_tasks: add_after_app_shutdown_clean_task(name) @wraps(func) def decorate(*args, **kwargs): return func(*args, **kwargs) return decorate ================================================ FILE: apps/ops/celery/heartbeat.py ================================================ from pathlib import Path from celery.signals import heartbeat_sent, worker_ready, worker_shutdown @heartbeat_sent.connect def heartbeat(sender, **kwargs): worker_name = sender.eventer.hostname.split('@')[0] heartbeat_path = Path('/opt/maxkb-app/tmp/worker_heartbeat_{}'.format(worker_name)) heartbeat_path.touch() @worker_ready.connect def worker_ready(sender, **kwargs): worker_name = sender.hostname.split('@')[0] ready_path = Path('/opt/maxkb-app/tmp/worker_ready_{}'.format(worker_name)) ready_path.touch() @worker_shutdown.connect def worker_shutdown(sender, **kwargs): worker_name = sender.hostname.split('@')[0] for signal in ['ready', 'heartbeat']: path = Path('/opt/maxkb-app/tmp/worker_{}_{}'.format(signal, worker_name)) path.unlink(missing_ok=True) ================================================ FILE: apps/ops/celery/hmac_signed_serializer.py ================================================ import hmac import hashlib import pickle import os import socket from kombu.serialization import register _local_secret_key = os.environ.get('MAXKB_HMAC_SIGNED_SERIALIZER_SECRET_KEY', 'default_hmac_signed_serializer_secret_key:' + os.getenv('MAXKB_VERSION', socket.gethostname())) try: from xpack import get_md5 _local_secret_key = get_md5() except ImportError: pass def secure_dumps(obj): data = pickle.dumps(obj) signature = hmac.new(_local_secret_key.encode(), data, hashlib.sha256).digest() return signature + data def secure_loads(signed_data): if len(signed_data) < 32: raise ValueError("Invalid signed data packet") signature = signed_data[:32] payload = signed_data[32:] expected_signature = hmac.new(_local_secret_key.encode(), payload, hashlib.sha256).digest() if hmac.compare_digest(signature, expected_signature): return pickle.loads(payload) else: raise ValueError("Security Alert: Task signature mismatch! Potential tampering detected.") def register_hmac_signed_serializer(): register( 'hmac_signed_serializer', secure_dumps, secure_loads, content_type='application/x-python-hmac-signed-serialize', content_encoding='binary' ) ================================================ FILE: apps/ops/celery/logger.py ================================================ import logging from logging import StreamHandler from threading import get_ident from celery import current_task from celery.signals import task_prerun, task_postrun from django.conf import settings from kombu import Connection, Exchange, Queue, Producer from kombu.mixins import ConsumerMixin from common.utils.logger import maxkb_logger from .utils import get_celery_task_log_path from .const import CELERY_LOG_MAGIC_MARK routing_key = 'celery_log' celery_log_exchange = Exchange('celery_log_exchange', type='direct') celery_log_queue = [Queue('celery_log', celery_log_exchange, routing_key=routing_key)] class CeleryLoggerConsumer(ConsumerMixin): def __init__(self): self.connection = Connection(settings.CELERY_LOG_BROKER_URL) def get_consumers(self, Consumer, channel): return [Consumer(queues=celery_log_queue, accept=['pickle', 'json'], callbacks=[self.process_task]) ] def handle_task_start(self, task_id, message): pass def handle_task_end(self, task_id, message): pass def handle_task_log(self, task_id, msg, message): pass def process_task(self, body, message): action = body.get('action') task_id = body.get('task_id') msg = body.get('msg') if action == CeleryLoggerProducer.ACTION_TASK_LOG: self.handle_task_log(task_id, msg, message) elif action == CeleryLoggerProducer.ACTION_TASK_START: self.handle_task_start(task_id, message) elif action == CeleryLoggerProducer.ACTION_TASK_END: self.handle_task_end(task_id, message) class CeleryLoggerProducer: ACTION_TASK_START, ACTION_TASK_LOG, ACTION_TASK_END = range(3) def __init__(self): self.connection = Connection(settings.CELERY_LOG_BROKER_URL) @property def producer(self): return Producer(self.connection) def publish(self, payload): self.producer.publish( payload, serializer='json', exchange=celery_log_exchange, declare=[celery_log_exchange], routing_key=routing_key ) def log(self, task_id, msg): payload = {'task_id': task_id, 'msg': msg, 'action': self.ACTION_TASK_LOG} return self.publish(payload) def read(self): pass def flush(self): pass def task_end(self, task_id): payload = {'task_id': task_id, 'action': self.ACTION_TASK_END} return self.publish(payload) def task_start(self, task_id): payload = {'task_id': task_id, 'action': self.ACTION_TASK_START} return self.publish(payload) class CeleryTaskLoggerHandler(StreamHandler): terminator = '\r\n' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) task_prerun.connect(self.on_task_start) task_postrun.connect(self.on_start_end) @staticmethod def get_current_task_id(): if not current_task: return task_id = current_task.request.root_id return task_id def on_task_start(self, sender, task_id, **kwargs): return self.handle_task_start(task_id) def on_start_end(self, sender, task_id, **kwargs): return self.handle_task_end(task_id) def after_task_publish(self, sender, body, **kwargs): pass def emit(self, record): task_id = self.get_current_task_id() if not task_id: return try: self.write_task_log(task_id, record) self.flush() except Exception: self.handleError(record) def write_task_log(self, task_id, msg): pass def handle_task_start(self, task_id): pass def handle_task_end(self, task_id): pass class CeleryThreadingLoggerHandler(CeleryTaskLoggerHandler): @staticmethod def get_current_thread_id(): return str(get_ident()) def emit(self, record): thread_id = self.get_current_thread_id() try: self.write_thread_task_log(thread_id, record) self.flush() except ValueError: self.handleError(record) def write_thread_task_log(self, thread_id, msg): pass def handle_task_start(self, task_id): pass def handle_task_end(self, task_id): pass def handleError(self, record) -> None: pass class CeleryTaskMQLoggerHandler(CeleryTaskLoggerHandler): def __init__(self): self.producer = CeleryLoggerProducer() super().__init__(stream=None) def write_task_log(self, task_id, record): msg = self.format(record) self.producer.log(task_id, msg) def flush(self): self.producer.flush() class CeleryTaskFileHandler(CeleryTaskLoggerHandler): def __init__(self, *args, **kwargs): self.f = None super().__init__(*args, **kwargs) def emit(self, record): msg = self.format(record) if not self.f or self.f.closed: return self.f.write(msg) self.f.write(self.terminator) self.flush() def flush(self): self.f and self.f.flush() def handle_task_start(self, task_id): log_path = get_celery_task_log_path(task_id) self.f = open(log_path, 'a') def handle_task_end(self, task_id): self.f and self.f.close() class CeleryThreadTaskFileHandler(CeleryThreadingLoggerHandler): def __init__(self, *args, **kwargs): self.thread_id_fd_mapper = {} self.task_id_thread_id_mapper = {} super().__init__(*args, **kwargs) def write_thread_task_log(self, thread_id, record): f = self.thread_id_fd_mapper.get(thread_id, None) if not f: raise ValueError('Not found thread task file') msg = self.format(record) f.write(msg.encode()) f.write(self.terminator.encode()) f.flush() def flush(self): for f in self.thread_id_fd_mapper.values(): f.flush() def handle_task_start(self, task_id): maxkb_logger.info('handle_task_start') log_path = get_celery_task_log_path(task_id) thread_id = self.get_current_thread_id() self.task_id_thread_id_mapper[task_id] = thread_id f = open(log_path, 'ab') self.thread_id_fd_mapper[thread_id] = f def handle_task_end(self, task_id): maxkb_logger.info('handle_task_end') ident_id = self.task_id_thread_id_mapper.get(task_id, '') f = self.thread_id_fd_mapper.pop(ident_id, None) if f and not f.closed: f.write(CELERY_LOG_MAGIC_MARK) f.close() self.task_id_thread_id_mapper.pop(task_id, None) ================================================ FILE: apps/ops/celery/signal_handler.py ================================================ # -*- coding: utf-8 -*- # import logging import os import uuid_utils.compat as uuid from celery import subtask from celery.signals import ( worker_ready, worker_shutdown, after_setup_logger, task_revoked, task_prerun ) from django.core.cache import cache from django_apscheduler.models import DjangoJob from django_celery_beat.models import PeriodicTask from common.utils.logger import maxkb_logger from .decorator import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks from .logger import CeleryThreadTaskFileHandler logger = logging.getLogger(__file__) safe_str = lambda x: x def init_scheduler(): from common import job from common.init import init_template from trigger.models import Trigger job.run() init_template.run() # 清理已经不存在的 trigger job trigger_jobs = DjangoJob.objects.filter(id__startswith="trigger:") # 从 job id 中提取 trigger_id (格式: trigger::task:...) trigger_ids_from_jobs = set() job_id_to_trigger_id = {} # 映射 job_id -> trigger_id for job in trigger_jobs: parts = job.id.split(':') if len(parts) >= 2: trigger_id = uuid.UUID(parts[1]) # 提取 trigger_id trigger_ids_from_jobs.add(trigger_id) job_id_to_trigger_id[job.id] = trigger_id # 获取所有有效的 Trigger ID valid_trigger_ids = set(Trigger.objects.filter( id__in=trigger_ids_from_jobs, is_active=True ).values_list('id', flat=True)) # 找出需要删除的 job (trigger 已不存在的) jobs_to_delete = [ job_id for job_id, trigger_id in job_id_to_trigger_id.items() if trigger_id not in valid_trigger_ids ] if jobs_to_delete: DjangoJob.objects.filter(id__in=jobs_to_delete).delete() logger.info(f"Cleaned up {len(jobs_to_delete)} orphaned trigger jobs") try: from xpack import job as xpack_job xpack_job.run() except ImportError: pass @worker_ready.connect def on_app_ready(sender=None, headers=None, **kwargs): if cache.get("CELERY_APP_READY", 0) == 1: return cache.set("CELERY_APP_READY", 1, 10) # 初始化定时任务 init_scheduler() tasks = get_after_app_ready_tasks() logger.debug("Work ready signal recv") logger.debug("Start need start task: [{}]".format(", ".join(tasks))) for task in tasks: periodic_task = PeriodicTask.objects.filter(task=task).first() if periodic_task and not periodic_task.enabled: logger.debug("Periodic task [{}] is disabled!".format(task)) continue subtask(task).delay() def delete_files(directory): if os.path.isdir(directory): for filename in os.listdir(directory): file_path = os.path.join(directory, filename) if os.path.isfile(file_path): os.remove(file_path) @worker_shutdown.connect def after_app_shutdown_periodic_tasks(sender=None, **kwargs): if cache.get("CELERY_APP_SHUTDOWN", 0) == 1: return cache.set("CELERY_APP_SHUTDOWN", 1, 10) tasks = get_after_app_shutdown_clean_tasks() logger.debug("Worker shutdown signal recv") logger.debug("Clean period tasks: [{}]".format(', '.join(tasks))) PeriodicTask.objects.filter(name__in=tasks).delete() @after_setup_logger.connect def add_celery_logger_handler(sender=None, logger=None, loglevel=None, format=None, **kwargs): if not logger: return task_handler = CeleryThreadTaskFileHandler() task_handler.setLevel(loglevel) formatter = logging.Formatter(format) task_handler.setFormatter(formatter) logger.addHandler(task_handler) @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): maxkb_logger.info('task_revoked', terminated) @task_prerun.connect def on_taskaa_start(sender, task_id, **kwargs): pass # sender.update_state(state='REVOKED', # meta={'exc_type': 'Exception', 'exc': 'Exception', 'message': '暂停任务', 'exc_message': ''}) ================================================ FILE: apps/ops/celery/utils.py ================================================ # -*- coding: utf-8 -*- # import logging import os import uuid from django.conf import settings from django_celery_beat.models import ( PeriodicTasks ) from common.utils.logger import maxkb_logger from maxkb.const import PROJECT_DIR logger = logging.getLogger(__file__) def disable_celery_periodic_task(task_name): from django_celery_beat.models import PeriodicTask PeriodicTask.objects.filter(name=task_name).update(enabled=False) PeriodicTasks.update_changed() def delete_celery_periodic_task(task_name): from django_celery_beat.models import PeriodicTask PeriodicTask.objects.filter(name=task_name).delete() PeriodicTasks.update_changed() def get_celery_periodic_task(task_name): from django_celery_beat.models import PeriodicTask task = PeriodicTask.objects.filter(name=task_name).first() return task def make_dirs(name, mode=0o700, exist_ok=False): """ 默认权限设置为 0o700 """ return os.makedirs(name, mode=mode, exist_ok=exist_ok) def get_task_log_path(base_path, task_id, level=2): task_id = str(task_id) try: uuid.UUID(task_id) except: return os.path.join(PROJECT_DIR, 'data', 'caution.txt') rel_path = os.path.join(*task_id[:level], task_id + '.log') path = os.path.join(base_path, rel_path) make_dirs(os.path.dirname(path), exist_ok=True) return path def get_celery_task_log_path(task_id): return get_task_log_path(settings.CELERY_LOG_DIR, task_id) def get_celery_status(): from . import app i = app.control.inspect() ping_data = i.ping() or {} active_nodes = [k for k, v in ping_data.items() if v.get('ok') == 'pong'] active_queue_worker = set([n.split('@')[0] for n in active_nodes if n]) # Celery Worker 数量: 2 if len(active_queue_worker) < 2: maxkb_logger.info("Not all celery worker worked") return False else: return True ================================================ FILE: apps/oss/__init__.py ================================================ ================================================ FILE: apps/oss/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/oss/apps.py ================================================ from django.apps import AppConfig class OssConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'oss' ================================================ FILE: apps/oss/migrations/__init__.py ================================================ ================================================ FILE: apps/oss/models.py ================================================ from django.db import models # Create your models here. ================================================ FILE: apps/oss/retrieval_urls.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: retrieval_urls.py @date:2025/7/2 19:01 @desc: """ from django.urls import re_path from . import views app_name = 'oss' urlpatterns = [ re_path(rf'^(.*)/oss/file/(?P[\w-]+)/?$', views.FileRetrievalView.as_view()), re_path(rf'oss/file/(?P[\w-]+)/?$', views.FileRetrievalView.as_view()), re_path(rf'^/oss/get_url/(?P[\w-]+)?$', views.GetUrlView.as_view()), ] ================================================ FILE: apps/oss/serializers/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/oss/serializers/file.py ================================================ # coding=utf-8 import base64 import ipaddress import re import socket import urllib from urllib.parse import urlparse import requests import uuid_utils.compat as uuid from django.db.models import QuerySet from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application from common.exception.app_exception import NotFound404, AppApiException from knowledge.models import File, FileSourceType from tools.serializers.tool import UploadedFileField mime_types = { "html": "text/html", "htm": "text/html", "shtml": "text/html", "css": "text/css", "xml": "text/xml", "gif": "image/gif", "jpeg": "image/jpeg", "jpg": "image/jpeg", "js": "application/javascript", "atom": "application/atom+xml", "rss": "application/rss+xml", "mml": "text/mathml", "txt": "text/plain", "jad": "text/vnd.sun.j2me.app-descriptor", "wml": "text/vnd.wap.wml", "htc": "text/x-component", "avif": "image/avif", "png": "image/png", "svg": "image/svg+xml", "svgz": "image/svg+xml", "tif": "image/tiff", "tiff": "image/tiff", "wbmp": "image/vnd.wap.wbmp", "webp": "image/webp", "ico": "image/x-icon", "jng": "image/x-jng", "bmp": "image/x-ms-bmp", "woff": "font/woff", "woff2": "font/woff2", "jar": "application/java-archive", "war": "application/java-archive", "ear": "application/java-archive", "json": "application/json", "hqx": "application/mac-binhex40", "doc": "application/msword", "pdf": "application/pdf", "ps": "application/postscript", "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "eps": "application/postscript", "ai": "application/postscript", "rtf": "application/rtf", "m3u8": "application/vnd.apple.mpegurl", "kml": "application/vnd.google-earth.kml+xml", "kmz": "application/vnd.google-earth.kmz", "xls": "application/vnd.ms-excel", "eot": "application/vnd.ms-fontobject", "ppt": "application/vnd.ms-powerpoint", "odg": "application/vnd.oasis.opendocument.graphics", "odp": "application/vnd.oasis.opendocument.presentation", "ods": "application/vnd.oasis.opendocument.spreadsheet", "odt": "application/vnd.oasis.opendocument.text", "wmlc": "application/vnd.wap.wmlc", "wasm": "application/wasm", "7z": "application/x-7z-compressed", "cco": "application/x-cocoa", "jardiff": "application/x-java-archive-diff", "jnlp": "application/x-java-jnlp-file", "run": "application/x-makeself", "pl": "application/x-perl", "pm": "application/x-perl", "prc": "application/x-pilot", "pdb": "application/x-pilot", "rar": "application/x-rar-compressed", "rpm": "application/x-redhat-package-manager", "sea": "application/x-sea", "swf": "application/x-shockwave-flash", "sit": "application/x-stuffit", "tcl": "application/x-tcl", "tk": "application/x-tcl", "der": "application/x-x509-ca-cert", "pem": "application/x-x509-ca-cert", "crt": "application/x-x509-ca-cert", "xpi": "application/x-xpinstall", "xhtml": "application/xhtml+xml", "xspf": "application/xspf+xml", "zip": "application/zip", "bin": "application/octet-stream", "exe": "application/octet-stream", "dll": "application/octet-stream", "deb": "application/octet-stream", "dmg": "application/octet-stream", "iso": "application/octet-stream", "img": "application/octet-stream", "msi": "application/octet-stream", "msp": "application/octet-stream", "msm": "application/octet-stream", "mid": "audio/midi", "midi": "audio/midi", "kar": "audio/midi", "mp3": "audio/mp3", "ogg": "audio/ogg", "m4a": "audio/x-m4a", "ra": "audio/x-realaudio", "3gpp": "video/3gpp", "3gp": "video/3gpp", "ts": "video/mp2t", "mp4": "video/mp4", "mpeg": "video/mpeg", "mpg": "video/mpeg", "mov": "video/quicktime", "webm": "video/webm", "flv": "video/x-flv", "m4v": "video/x-m4v", "mng": "video/x-mng", "asx": "video/x-ms-asf", "asf": "video/x-ms-asf", "wmv": "video/x-ms-wmv", "avi": "video/x-msvideo", "wav": "audio/wav", "flac": "audio/flac", "aac": "audio/aac", "opus": "audio/opus", "csv": "text/csv", "tsv": "text/tab-separated-values", "ics": "text/calendar", } # 如果是音频文件并且有range请求,处理部分内容 audio_types = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'opus', 'm4a'] class FileSerializer(serializers.Serializer): file = UploadedFileField(required=True, label=_('file')) meta = serializers.JSONField(required=False, allow_null=True) source_id = serializers.CharField( required=False, allow_null=True, label=_('source id'), default=FileSourceType.TEMPORARY_120_MINUTE.value ) source_type = serializers.ChoiceField( choices=FileSourceType.choices, required=False, allow_null=True, label=_('source type'), default=FileSourceType.TEMPORARY_120_MINUTE ) def upload(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) meta = self.data.get('meta', None) if not meta: meta = {'debug': True} file_id = meta.get('file_id', uuid.uuid7()) file = File( id=file_id, file_name=self.data.get('file').name, meta=meta, source_id=self.data.get('source_id') or FileSourceType.TEMPORARY_120_MINUTE.value, source_type=self.data.get('source_type') or FileSourceType.TEMPORARY_120_MINUTE ) file.save(self.data.get('file').read()) return f'./oss/file/{file_id}' class Operate(serializers.Serializer): id = serializers.UUIDField(required=True) http_range = serializers.CharField( required=False, allow_blank=True, allow_null=True, label=_('HTTP Range'), help_text=_('HTTP Range header for partial content requests, e.g., "bytes=0-1023"') ) def get(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) file_id = self.data.get('id') file = QuerySet(File).filter(id=file_id).first() if file is None: raise NotFound404(404, _('File not found')) file_type = file.file_name.split(".")[-1].lower() content_type = mime_types.get(file_type, 'application/octet-stream') encoded_filename = urllib.parse.quote(file.file_name) # 获取文件内容 file_bytes = file.get_bytes() file_size = len(file_bytes) response = None if file_type in audio_types and self.data.get('http_range'): response = self.handle_audio(file_size, file_bytes, content_type, encoded_filename) if response: return response # 对于非范围请求或其他类型文件,返回完整内容 headers = { 'Content-Type': content_type, 'Content-Disposition': f'{"inline" if file_type == "pdf" else "attachment"}; filename={encoded_filename}' } return HttpResponse( file_bytes, status=200, headers=headers ) def handle_audio(self, file_size, file_bytes, content_type, encoded_filename): # 解析range请求 (格式如 "bytes=0-1023") range_match = re.match(r'bytes=(\d+)-(\d*)', self.data.get('http_range', '')) if range_match: start = int(range_match.group(1)) end = int(range_match.group(2)) if range_match.group(2) else file_size - 1 # 确保范围合法 end = min(end, file_size - 1) length = end - start + 1 # 创建部分响应 response = HttpResponse( file_bytes[start:start + length], status=206, content_type=content_type ) # 设置部分内容响应头 response['Content-Range'] = f'bytes {start}-{end}/{file_size}' response['Accept-Ranges'] = 'bytes' response['Content-Length'] = str(length) response['Content-Disposition'] = f'inline; filename={encoded_filename}' return response def delete(self): self.is_valid(raise_exception=True) file_id = self.data.get('id') file = QuerySet(File).filter(id=file_id).first() if file is not None: file.delete() return True def get_url_content(url, application_id: str): application = Application.objects.filter(id=application_id).first() if application is None: return AppApiException(500, _('Application does not exist')) if not application.file_upload_enable: return AppApiException(500, _('File upload is not enabled')) file_limit = 50 * 1024 * 1024 if application.file_upload_setting and application.file_upload_setting.get('fileLimit'): file_limit = application.file_upload_setting.get('fileLimit') * 1024 * 1024 parsed = validate_url(url) response = requests.get( url, timeout=3, allow_redirects=False ) final_host = urlparse(response.url).hostname if is_private_ip(final_host): raise ValueError("Blocked unsafe redirect to internal host") # 判断文件大小 if int(response.headers.get('Content-Length', 0)) > file_limit: raise AppApiException(500, _('File size exceeds limit')) # 返回状态码 响应内容大小 响应的contenttype 还有字节流 content_type = response.headers.get('Content-Type', '') # 根据内容类型决定如何处理 if 'text' in content_type or 'json' in content_type: content = response.text else: # 二进制内容使用Base64编码 content = base64.b64encode(response.content).decode('utf-8') return { 'status_code': response.status_code, 'Content-Length': response.headers.get('Content-Length', 0), 'Content-Type': content_type, 'content': content, } def is_private_ip(host: str) -> bool: """检测 IP 是否属于内网、环回、云 metadata 的危险地址""" try: ip = ipaddress.ip_address(socket.gethostbyname(host)) return ( ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local or ip.is_multicast ) except Exception: return True def validate_url(url: str): """验证 URL 是否安全""" if not url: raise ValueError("URL is required") parsed = urlparse(url) # 仅允许 http / https if parsed.scheme not in ("http", "https"): raise ValueError("Only http and https are allowed") host = parsed.hostname # 域名不能为空 if not host: raise ValueError("Invalid URL") # 禁止访问内部、保留、环回、云 metadata if is_private_ip(host): raise ValueError("Access to internal IP addresses is blocked") return parsed ================================================ FILE: apps/oss/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/oss/urls.py ================================================ from django.urls import path from . import views app_name = 'oss' urlpatterns = [ path('oss/file', views.FileView.as_view()), path('oss/get_url/', views.GetUrlView.as_view()), ] ================================================ FILE: apps/oss/views/__init__.py ================================================ from .file import * ================================================ FILE: apps/oss/views/file.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.parsers import MultiPartParser from rest_framework.views import APIView from rest_framework.views import Request from common.auth import TokenAuth, AllTokenAuth from common.log.log import log from common.result import result from knowledge.api.file import FileUploadAPI, FileGetAPI from oss.serializers.file import FileSerializer, get_url_content class FileRetrievalView(APIView): @extend_schema( methods=['GET'], summary=_('Get file'), description=_('Get file'), operation_id=_('Get file'), # type: ignore parameters=FileGetAPI.get_parameters(), responses=FileGetAPI.get_response(), tags=[_('File')] # type: ignore ) def get(self, request: Request, file_id: str): return FileSerializer.Operate(data={ 'id': file_id, 'http_range': request.headers.get('Range', ''), }).get() class FileView(APIView): authentication_classes = [AllTokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['POST'], summary=_('Upload file'), description=_('Upload file'), operation_id=_('Upload file'), # type: ignore parameters=FileUploadAPI.get_parameters(), request=FileUploadAPI.get_request(), responses=FileUploadAPI.get_response(), tags=[_('File')] # type: ignore ) @log(menu='file', operate='Upload file') def post(self, request: Request): return result.success(FileSerializer(data={ 'file': request.FILES.get('file'), 'source_id': request.data.get('source_id'), 'source_type': request.data.get('source_type'), }).upload()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['DELETE'], summary=_('Delete file'), description=_('Delete file'), operation_id=_('Delete file'), # type: ignore parameters=FileGetAPI.get_parameters(), responses=FileGetAPI.get_response(), tags=[_('File')] # type: ignore ) @log(menu='file', operate='Delete file') def delete(self, request: Request, file_id: str): return result.success(FileSerializer.Operate(data={'id': file_id}).delete()) class GetUrlView(APIView): authentication_classes = [AllTokenAuth] @extend_schema( methods=['GET'], summary=_('Get url'), description=_('Get url'), operation_id=_('Get url'), # type: ignore tags=[_('Chat')] # type: ignore ) def get(self, request: Request, application_id: str): url = request.query_params.get('url') result_data = get_url_content(url, application_id) return result.success(result_data) ================================================ FILE: apps/oss/views.py ================================================ from django.shortcuts import render # Create your views here. ================================================ FILE: apps/system_manage/__init__.py ================================================ ================================================ FILE: apps/system_manage/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/system_manage/api/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/4/28 17:05 @desc: """ ================================================ FILE: apps/system_manage/api/email_setting.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_user_resource_permission.py @date:2025/4/28 18:13 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from system_manage.serializers.email_setting import EmailSettingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionResponse, \ UpdateUserResourcePermissionRequest class EmailResponse(ResultSerializer): def get_data(self): return EmailSettingSerializer.Create() class EmailSettingAPI(APIMixin): @staticmethod def get_request(): return EmailSettingSerializer.Create() @staticmethod def get_response(): return EmailResponse ================================================ FILE: apps/system_manage/api/resource_mapping.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: resource_mapping.py @date:2025/12/26 14:07 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from common.mixins.api_mixin import APIMixin class ResourceMappingResponse(serializers.Serializer): id = serializers.UUIDField(required=True, label="主键id") target_id = serializers.CharField(required=True, label="被关联资源名称") target_type = serializers.CharField(required=True, label="被关联资源类型") source_id = serializers.CharField(required=True, label="关联资源Id") source_type = serializers.CharField(required=True, label="关联资源类型") name = serializers.CharField(required=True, label="名称") desc = serializers.CharField(required=False, label="描述") user_id = serializers.UUIDField(required=True, label="主键id") class ResourceMappingAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source", description="资源类型", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_id", description="资源id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="resource_name", description="名称", type=OpenApiTypes.STR, location='query', required=False ), ] @staticmethod def get_response(): return ResourceMappingResponse(many=True) ================================================ FILE: apps/system_manage/api/system.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: system_setting.py @date:2025/6/4 16:34 @desc: """ from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from system_manage.serializers.system import SystemProfileResponseSerializer class SystemProfileResult(ResultSerializer): def get_data(self): return SystemProfileResponseSerializer() class SystemProfileAPI(APIMixin): @staticmethod def get_response(): return SystemProfileResult ================================================ FILE: apps/system_manage/api/user_resource_permission.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_user_resource_permission.py @date:2025/4/28 18:13 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer, PageDataResponse from system_manage.serializers.user_resource_permission import ResourceUserPermissionEditRequest, UpdateTeamMemberItemPermissionSerializer class UserResourcePermissionResponse0(serializers.Serializer): id = serializers.UUIDField(required=True, label="主键id") name = serializers.CharField(required=True, label="资源名称") auth_target_type = serializers.CharField(required=True, label="授权资源") user_id = serializers.UUIDField(required=True, label="用户id") icon = serializers.CharField(required=True, label="资源图标") auth_type = serializers.CharField(required=True, label="授权类型") permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) class NewAPIUserResourcePermissionResponse(ResultSerializer): def get_data(self): return UserResourcePermissionResponse0(many=True) class NewAPIUserResourcePermissionPageResponse(ResultPageSerializer): def get_data(self): return UserResourcePermissionResponse0(many=True) class UserResourcePermissionAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="user_id", description="用户id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="name", description="名称", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="permission", description="权限", type=OpenApiTypes.STR, location='query', many=True, required=False ), ] @staticmethod def get_response(): return NewAPIUserResourcePermissionResponse class EditUserResourcePermissionAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="user_id", description="用户id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="resource", description="资源类型", type=OpenApiTypes.STR, location='path', required=True ), ] @staticmethod def get_request(): return UpdateTeamMemberItemPermissionSerializer(many=True) @staticmethod def get_response(): return NewAPIUserResourcePermissionResponse class ResourceUserPermissionResponse(serializers.Serializer): id = serializers.CharField(required=True, label=_('user id')) nick_name = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('nick_name')) username = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('username')) permission = serializers.CharField(required=True, label=_('permission')) class APIResourceUserPermissionResponse(ResultSerializer): def get_data(self): return ResourceUserPermissionResponse(many=True) class ResourceUserPermissionAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="target", description="资源id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="resource", description="资源类型", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="username", description="用户名", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="nick_name", description="姓名", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="permission", description="权限", type=OpenApiTypes.STR, location='query', many=True, required=False ), ] @staticmethod def get_response(): return APIResourceUserPermissionResponse class UserResourcePermissionPageAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="user_id", description="用户id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="resource", description="资源类型", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="name", description="资源名称", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="permission[]", description="权限", type=OpenApiTypes.STR, location='query', many=True, required=False ), ] @staticmethod def get_response(): return NewAPIUserResourcePermissionPageResponse class APIResourceUserPermissionPageResponse(ResultPageSerializer): def get_data(self): return PageDataResponse(ResourceUserPermissionResponse(many=True)) class ResourceUserPermissionPageAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="target", description="资源id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="resource", description="资源类型", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="username", description="用户名", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="nick_name", description="姓名", type=OpenApiTypes.STR, location='query', required=False ), OpenApiParameter( name="permission[]", description="权限", type=OpenApiTypes.STR, location='query', many=True, required=False ), ] @staticmethod def get_response(): return APIResourceUserPermissionPageResponse class ResourceUserPermissionEditAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="target", description="资源id", type=OpenApiTypes.STR, location='path', required=True ), OpenApiParameter( name="resource", description="资源类型", type=OpenApiTypes.STR, location='path', required=True ), ] @staticmethod def get_request(): return ResourceUserPermissionEditRequest(required=True, many=True, label=_('users_permission')) @staticmethod def get_response(): return APIResourceUserPermissionResponse() ================================================ FILE: apps/system_manage/apps.py ================================================ from django.apps import AppConfig class SystemManageConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'system_manage' ================================================ FILE: apps/system_manage/migrations/0001_initial.py ================================================ # Generated by Django 5.2.4 on 2025-07-14 03:50 import common.encoder.encoder import django.contrib.postgres.fields import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='ChatUser', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, verbose_name='邮箱')), ('phone', models.CharField(default='', max_length=20, verbose_name='电话')), ('nick_name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='昵称')), ('username', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='用户名')), ('password', models.CharField(max_length=150, verbose_name='密码')), ('source', models.CharField(db_index=True, default='LOCAL', max_length=10, verbose_name='来源')), ('is_active', models.BooleanField(db_index=True, default=True)), ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, null=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, null=True, verbose_name='修改时间')), ], options={ 'db_table': 'chat_user', }, ), migrations.CreateModel( name='Log', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('menu', models.CharField(max_length=128, verbose_name='操作菜单')), ('operate', models.CharField(db_index=True, max_length=128, verbose_name='操作')), ('operation_object', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='操作对象')), ('user', models.JSONField(default=dict, verbose_name='用户信息')), ('status', models.IntegerField(db_index=True, verbose_name='状态')), ('ip_address', models.CharField(max_length=128, verbose_name='ip地址')), ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='详情')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ], options={ 'db_table': 'log', }, ), migrations.CreateModel( name='SystemSetting', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('type', models.IntegerField(choices=[(0, '邮箱'), (1, '私钥秘钥')], default=0, primary_key=True, serialize=False, verbose_name='设置类型')), ('meta', models.JSONField(default=dict, verbose_name='配置数据')), ], options={ 'db_table': 'system_setting', }, ), migrations.CreateModel( name='UserGroup', fields=[ ('id', models.CharField(default=uuid_utils.compat.uuid7, editable=False, max_length=128, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='名称')), ], options={ 'db_table': 'user_group', }, ), migrations.CreateModel( name='UserGroupRelation', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.chatuser', verbose_name='用户')), ], options={ 'db_table': 'user_group_relation', }, ), migrations.CreateModel( name='WorkspaceUserResourcePermission', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=128, verbose_name='工作空间id')), ('auth_target_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')], db_index=True, default='KNOWLEDGE', max_length=128, verbose_name='授权目标')), ('target', models.UUIDField(db_index=True, verbose_name='知识库/应用id')), ('auth_type', models.CharField(choices=[('ROLE', 'Role'), ('RESOURCE_PERMISSION_GROUP', 'Resource Permission Group')], db_default='ROLE', db_index=True, default=False, verbose_name='授权类型')), ('permission_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, choices=[('VIEW', 'View'), ('MANAGE', 'Manage'), ('ROLE', 'Role')], default='VIEW', max_length=256), default=list, size=None, verbose_name='权限列表')), ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='工作空间下的用户')), ], options={ 'db_table': 'workspace_user_resource_permission', }, ), migrations.CreateModel( name='ResourceChatUserGroupAuthorize', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, null=True, verbose_name='工作空间id')), ('resource_id', models.UUIDField(db_index=True, verbose_name='资源id')), ('resource_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用')], db_index=True, verbose_name='资源类型')), ('is_auth', models.BooleanField(verbose_name='是否授权')), ('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')), ], options={ 'db_table': 'resource_chat_user_group_authorize', 'unique_together': {('user_group_id', 'resource_type', 'resource_id')}, }, ), migrations.CreateModel( name='ResourceChatUserAuthorize', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, null=True, verbose_name='工作空间id')), ('resource_id', models.UUIDField(db_index=True, verbose_name='资源id')), ('resource_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用')], db_index=True, verbose_name='资源类型')), ('is_auth', models.BooleanField(verbose_name='是否授权')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.chatuser', verbose_name='用户')), ('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')), ], options={ 'db_table': 'resource_chat_user_authorize', 'unique_together': {('user_group_id', 'resource_type', 'resource_id', 'user_id')}, }, ), ] ================================================ FILE: apps/system_manage/migrations/0002_refresh_collation_reindex.py ================================================ import logging import psycopg from django.db import migrations from maxkb.const import CONFIG def get_connect(db_name): conn_params = { "dbname": db_name, "user": CONFIG.get('DB_USER'), "password": CONFIG.get('DB_PASSWORD'), "host": CONFIG.get('DB_HOST'), "port": CONFIG.get('DB_PORT') } # 建立连接 connect = psycopg.connect(**conn_params) return connect def sql_execute(conn, reindex_sql: str, alter_database_sql: str): """ 执行一条sql @param reindex_sql: @param conn: @param alter_database_sql: """ conn.autocommit = True with conn.cursor() as cursor: cursor.execute(reindex_sql, []) cursor.execute(alter_database_sql, []) cursor.close() def re_index(apps, schema_editor): app_db_name = CONFIG.get('DB_NAME') try: re_index_database(app_db_name) except Exception as e: logging.error(f'reindex database {app_db_name}发送错误:{str(e)}') try: re_index_database('root') except Exception as e: logging.error(f'reindex database root 发送错误:{str(e)}') def re_index_database(db_name): db_conn = get_connect(db_name) sql_execute(db_conn, f'REINDEX DATABASE "{db_name}";', f'ALTER DATABASE "{db_name}" REFRESH COLLATION VERSION;') db_conn.close() class Migration(migrations.Migration): dependencies = [ ("system_manage", "0001_initial"), ] operations = [ migrations.RunPython(re_index, atomic=False) ] ================================================ FILE: apps/system_manage/migrations/0003_alter_workspaceuserresourcepermission_target.py ================================================ # Generated by Django 5.2.6 on 2025-10-11 02:54 from concurrent.futures import ThreadPoolExecutor from functools import reduce from django.db import migrations, models from django.db.models import QuerySet from common.constants.permission_constants import WorkspaceUserRoleMapping from common.utils.common import group_by def workspace_user_role_mapping_model_exists(workspace_user_role_mapping_model): try: QuerySet(workspace_user_role_mapping_model).first() except Exception as e: return False return False def delete_auth(apps,folder_model): workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission') QuerySet(workspace_user_resource_permission_model).filter(target__in=QuerySet(folder_model).values_list('id')).delete() def get_workspace_user_resource_permission_list(apps, auth_target_type, workspace_user_role_mapping_model_workspace_dict, folder_model): workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission') return reduce(lambda x, y: [*x, *y], [ [workspace_user_resource_permission_model(target=f.id, workspace_id=f.workspace_id, user_id=wurm.user_id, auth_target_type=auth_target_type, auth_type="RESOURCE_PERMISSION_GROUP", permission_list=['VIEW','MANAGE'] if wurm.user_id == f.user_id else ['VIEW']) for wurm in workspace_user_role_mapping_model_workspace_dict.get(f.workspace_id, [])] for f in QuerySet(folder_model).all()], []) def auth_folder(apps, schema_editor): from common.database_model_manage.database_model_manage import DatabaseModelManage DatabaseModelManage.init() user_model = apps.get_model('users', 'User') application_folder_model = apps.get_model('application', 'ApplicationFolder') knowledge_folder_model = apps.get_model('knowledge', 'KnowledgeFolder') tool_folder_model = apps.get_model('tools', 'ToolFolder') workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission') workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") with ThreadPoolExecutor(max_workers=3) as executor: future = executor.submit(workspace_user_role_mapping_model_exists, workspace_user_role_mapping_model) exists = future.result() if not exists: workspace_user_role_mapping_model = None if workspace_user_role_mapping_model is None: workspace_user_role_mapping_model_workspace_dict = { 'default': [WorkspaceUserRoleMapping('default', '', u.id) for u in QuerySet(user_model).all()]} else: workspace_user_role_mapping_model_workspace_dict = group_by( [v for v in {str(wurm.user_id) + str(wurm.workspace_id): wurm for wurm in QuerySet(workspace_user_role_mapping_model)}.values()], lambda item: item.workspace_id) workspace_user_resource_permission_list = get_workspace_user_resource_permission_list(apps,"APPLICATION", workspace_user_role_mapping_model_workspace_dict, application_folder_model) workspace_user_resource_permission_list += get_workspace_user_resource_permission_list(apps,"TOOL", workspace_user_role_mapping_model_workspace_dict, tool_folder_model) workspace_user_resource_permission_list += get_workspace_user_resource_permission_list(apps,"KNOWLEDGE", workspace_user_role_mapping_model_workspace_dict, knowledge_folder_model) delete_auth(apps,application_folder_model) delete_auth(apps,knowledge_folder_model) delete_auth(apps,tool_folder_model) QuerySet(workspace_user_resource_permission_model).bulk_create(workspace_user_resource_permission_list) class Migration(migrations.Migration): dependencies = [ ('system_manage', '0002_refresh_collation_reindex'), ('tools', '0001_initial'), ('application', '0001_initial'), ('knowledge', '0001_initial'), ] operations = [ migrations.AlterField( model_name='workspaceuserresourcepermission', name='target', field=models.CharField(db_index=True, max_length=128, verbose_name='知识库/应用id'), ), migrations.RunPython(auth_folder, atomic=False) ] ================================================ FILE: apps/system_manage/migrations/0004_alter_systemsetting_type_and_more.py ================================================ # Generated by Django 5.2.7 on 2025-10-16 03:21 from django.db import migrations, models from django.db.models.functions import RowNumber def remove_duplicates(apps, schema_editor): from django.db.models import Window, F workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission') duplicates = workspace_user_resource_permission_model.objects.annotate( row_num=Window( expression=RowNumber(), partition_by=[F('workspace_id'), F('user'), F('auth_target_type'), F('target')], order_by=[F('create_time').desc()], ) ).filter(row_num__gt=1) ids_to_delete = list(duplicates.values_list('id', flat=True)) if ids_to_delete: workspace_user_resource_permission_model.objects.filter(id__in=ids_to_delete).delete() class Migration(migrations.Migration): dependencies = [ ('system_manage', '0003_alter_workspaceuserresourcepermission_target'), ('users', '0001_initial'), ] operations = [ migrations.RunPython(remove_duplicates), migrations.AlterUniqueTogether( name='workspaceuserresourcepermission', unique_together={('workspace_id', 'user', 'auth_target_type', 'target')}, ), migrations.AlterField( model_name='systemsetting', name='type', field=models.IntegerField(choices=[(0, '邮箱'), (1, '私钥秘钥'), (2, '日志清理时间')], default=0, primary_key=True, serialize=False, verbose_name='设置类型'), ), ] ================================================ FILE: apps/system_manage/migrations/0005_resourcemapping.py ================================================ # Generated by Django 5.2.8 on 2025-12-19 09:37 from concurrent.futures import ThreadPoolExecutor import uuid_utils.compat from django.db import migrations, models from django.db.models import QuerySet from knowledge.models import Knowledge def get_initialization_resource_mapping(): from django.db.models import QuerySet from application.flow.tools import get_workflow_resource, get_node_handle_callback, \ get_instance_resource from system_manage.models.resource_mapping import ResourceType from application.models import Application from knowledge.models import KnowledgeWorkflow from application.flow.tools import application_instance_field_call_dict, knowledge_instance_field_call_dict from application.models.application import ApplicationKnowledgeMapping from system_manage.models.resource_mapping import ResourceMapping resource_mapping_list = [] ids = list(Application.objects.values_list('id', flat=True)) for app_id in ids: try: application = Application.objects.get(id=app_id) workflow_mapping = get_workflow_resource(application.work_flow, get_node_handle_callback(ResourceType.APPLICATION, application.id)) instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id), application_instance_field_call_dict) resource_mapping_list += workflow_mapping resource_mapping_list += instance_mapping except: pass knowledge_ids = list(Knowledge.objects.values_list('id', flat=True)) for knowledge_id in knowledge_ids: try: knowledge = Knowledge.objects.get(id=knowledge_id) if knowledge.type == 4: knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_id).first() if knowledge_workflow: workflow_mapping = get_workflow_resource(knowledge_workflow.work_flow, get_node_handle_callback(ResourceType.KNOWLEDGE, str(knowledge_workflow.knowledge_id))) resource_mapping_list += workflow_mapping instance_mapping = get_instance_resource(knowledge, ResourceType.KNOWLEDGE, str(knowledge.id), knowledge_instance_field_call_dict) resource_mapping_list += instance_mapping except: pass application_knowledge_mapping = [ ResourceMapping(source_type=ResourceType.APPLICATION, target_type=ResourceType.KNOWLEDGE, source_id=str(akm.application_id), target_id=str(akm.knowledge_id)) for akm in QuerySet(ApplicationKnowledgeMapping).all()] resource_mapping_list += application_knowledge_mapping return {(str(item.target_type) + str(item.target_id) + str(item.source_type) + str(item.source_id)): item for item in resource_mapping_list}.values() def resource_mapping(apps, schema_editor): from system_manage.models.resource_mapping import ResourceMapping with ThreadPoolExecutor(max_workers=3) as executor: future = executor.submit(get_initialization_resource_mapping) resource_mapping_list = future.result() QuerySet(ResourceMapping).bulk_create(resource_mapping_list) class Migration(migrations.Migration): dependencies = [ ('system_manage', '0004_alter_systemsetting_type_and_more'), ('knowledge', '0007_remove_knowledgeworkflowversion_workflow_and_more'), ('application', '0003_application_stt_model_params_setting_and_more'), ] operations = [ migrations.CreateModel( name='ResourceMapping', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('source_type', models.CharField( choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')], db_index=True, verbose_name='关联资源类型')), ('target_type', models.CharField( choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')], db_index=True, verbose_name='被关联资源类型')), ('source_id', models.CharField(db_index=True, max_length=128, verbose_name='关联资源id')), ('target_id', models.CharField(db_index=True, max_length=128, verbose_name='被关联资源id')), ], options={ 'db_table': 'resource_mapping', }, ), migrations.RunPython(resource_mapping, atomic=False) ] ================================================ FILE: apps/system_manage/migrations/__init__.py ================================================ ================================================ FILE: apps/system_manage/models/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/16 18:23 @desc: """ from .workspace_user_permission import * from .system_setting import * from .log_management import * from .chat_user import * ================================================ FILE: apps/system_manage/models/chat_user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 10:20 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.constants.permission_constants import Group class ChatUser(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="") nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") source = models.CharField(max_length=10, verbose_name="来源", default="LOCAL", db_index=True) is_active = models.BooleanField(default=True, db_index=True) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True, db_index=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True, db_index=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [] class Meta: db_table = "chat_user" class UserGroup(models.Model): id = models.CharField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") name = models.CharField(max_length=150, verbose_name="名称", unique=True, db_index=True) class Meta: db_table = "user_group" class UserGroupRelation(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") user = models.ForeignKey(ChatUser, on_delete=models.CASCADE, verbose_name="用户") group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name="用户组") class Meta: db_table = "user_group_relation" class ResourceType(models.TextChoices): """资源类型""" KNOWLEDGE = Group.KNOWLEDGE.value, '知识库' APPLICATION = Group.APPLICATION.value, '应用' class ResourceChatUserAuthorize(models.Model): """ 资源对话用户授权表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True, null=True) user_group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name="用户组") user = models.ForeignKey(ChatUser, on_delete=models.CASCADE, verbose_name="用户") resource_id = models.UUIDField(max_length=128, verbose_name="资源id", db_index=True) resource_type = models.CharField(verbose_name="资源类型", choices=ResourceType.choices, db_index=True) is_auth = models.BooleanField(verbose_name="是否授权") class Meta: db_table = "resource_chat_user_authorize" unique_together = ('user_group_id', 'resource_type', 'resource_id', 'user_id') class ResourceChatUserGroupAuthorize(models.Model): """ 资源对话用户组授权表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True, null=True) user_group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name="用户组") resource_id = models.UUIDField(max_length=128, verbose_name="资源id", db_index=True) resource_type = models.CharField(verbose_name="资源类型", choices=ResourceType.choices, db_index=True) is_auth = models.BooleanField(verbose_name="是否授权") class Meta: db_table = "resource_chat_user_group_authorize" unique_together = ('user_group_id', 'resource_type', 'resource_id') ================================================ FILE: apps/system_manage/models/log_management.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: log_management.py @date:2025/6/4 14:15 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.encoder.encoder import SystemEncoder from common.mixins.app_model_mixin import AppModelMixin class Log(AppModelMixin): """ 审计日志 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") menu = models.CharField(max_length=128, verbose_name="操作菜单") operate = models.CharField(max_length=128, verbose_name="操作", db_index=True) operation_object = models.JSONField(verbose_name="操作对象", default=dict, encoder=SystemEncoder) user = models.JSONField(verbose_name="用户信息", default=dict) status = models.IntegerField(verbose_name="状态", db_index=True) ip_address = models.CharField(max_length=128, verbose_name="ip地址") details = models.JSONField(verbose_name="详情", default=dict, encoder=SystemEncoder) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) class Meta: db_table = "log" ================================================ FILE: apps/system_manage/models/resource_mapping.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: resource_mapping.py @date:2025/12/19 15:41 @desc: """ from django.db import models import uuid_utils.compat as uuid from common.constants.permission_constants import Group from common.mixins.app_model_mixin import AppModelMixin class ResourceType(models.TextChoices): KNOWLEDGE = Group.KNOWLEDGE.value, '知识库' APPLICATION = Group.APPLICATION.value, '应用' TOOL = Group.TOOL.value, '工具' MODEL = Group.MODEL.value, '模型' class ResourceMapping(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") source_type = models.CharField(verbose_name="关联资源类型", choices=ResourceType.choices, db_index=True) target_type = models.CharField(verbose_name="被关联资源类型", choices=ResourceType.choices, db_index=True) source_id = models.CharField(max_length=128, verbose_name="关联资源id", db_index=True) target_id = models.CharField(max_length=128, verbose_name="被关联资源id", db_index=True) class Meta: db_table = "resource_mapping" ================================================ FILE: apps/system_manage/models/system_setting.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: system_management.py @date:2024/3/19 13:47 @desc: 邮箱管理 """ from django.db import models from common.mixins.app_model_mixin import AppModelMixin class SettingType(models.IntegerChoices): """系统设置类型""" EMAIL = 0, '邮箱' RSA = 1, "私钥秘钥" LOG = 2, "日志清理时间" class SystemSetting(AppModelMixin): """ 系统设置 """ type = models.IntegerField(primary_key=True, verbose_name='设置类型', choices=SettingType.choices, default=SettingType.EMAIL) meta = models.JSONField(verbose_name="配置数据", default=dict) class Meta: db_table = "system_setting" ================================================ FILE: apps/system_manage/models/workspace_user_permission.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_permission.py @date:2025/4/16 18:25 @desc: """ import uuid_utils.compat as uuid from django.contrib.postgres.fields import ArrayField from django.db import models from common.constants.permission_constants import Group, ResourcePermissionGroup, ResourceAuthType, \ ResourcePermissionRole, ResourcePermission from users.models import User class AuthTargetType(models.TextChoices): """授权目标""" KNOWLEDGE = Group.KNOWLEDGE.value, '知识库' APPLICATION = Group.APPLICATION.value, '应用' TOOL = Group.TOOL.value, '工具' MODEL = Group.MODEL.value, '模型' class WorkspaceUserResourcePermission(models.Model): """ 工作空间用户资源权限表 用于管理当前工作空间是否有权限操作 某一个应用或者知识库 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=128, verbose_name="工作空间id", default="default", db_index=True) user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="工作空间下的用户") auth_target_type = models.CharField(verbose_name='授权目标', max_length=128, choices=AuthTargetType.choices, default=AuthTargetType.KNOWLEDGE, db_index=True) # 授权的知识库或者应用的id target = models.CharField(max_length=128, verbose_name="知识库/应用id", db_index=True) # 授权类型 如果是Role那么就是角色的权限 如果是PERMISSION auth_type = models.CharField(default=False, verbose_name="授权类型", choices=ResourceAuthType.choices, db_default=ResourceAuthType.ROLE, db_index=True) # 资源权限列表 permission_list = ArrayField(verbose_name="权限列表", default=list, base_field=models.CharField(max_length=256, blank=True, choices=ResourcePermission.choices + ResourcePermissionRole.choices, default=ResourcePermission.VIEW)) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, db_index=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, db_index=True) class Meta: db_table = "workspace_user_resource_permission" unique_together = ('workspace_id', 'user', 'auth_target_type', 'target') ================================================ FILE: apps/system_manage/serializers/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py @date:2025/4/28 17:05 @desc: """ ================================================ FILE: apps/system_manage/serializers/email_setting.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: system_setting.py @date:2024/3/19 16:29 @desc: """ import logging from django.core.mail.backends.smtp import EmailBackend from django.db.models import QuerySet from rest_framework import serializers from common.exception.app_exception import AppApiException from django.utils.translation import gettext_lazy as _ from common.utils.logger import maxkb_logger from system_manage.models import SystemSetting, SettingType class EmailSettingSerializer(serializers.Serializer): @staticmethod def one(): system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first() if system_setting is None: return {} return system_setting.meta class Create(serializers.Serializer): email_host = serializers.CharField(required=True, label=_('SMTP host')) email_port = serializers.IntegerField(required=True, label=_('SMTP port')) email_host_user = serializers.CharField(required=True, label=_('Sender\'s email')) email_host_password = serializers.CharField(required=True, label=_('Password')) email_use_tls = serializers.BooleanField(required=True, label=_('Whether to enable TLS')) email_use_ssl = serializers.BooleanField(required=True, label=_('Whether to enable SSL')) from_email = serializers.EmailField(required=True, label=_('Sender\'s email')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) try: EmailBackend(self.data.get("email_host"), self.data.get("email_port"), self.data.get("email_host_user"), self.data.get("email_host_password"), self.data.get("email_use_tls"), False, self.data.get("email_use_ssl") ).open() except Exception as e: maxkb_logger.error(f'Exception: {e}') raise AppApiException(1004, _('Email verification failed')) def update_or_save(self): self.is_valid(raise_exception=True) system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first() if system_setting is None: system_setting = SystemSetting(type=SettingType.EMAIL.value) system_setting.meta = self.to_email_meta() system_setting.save() return system_setting.meta def to_email_meta(self): return {'email_host': self.data.get('email_host'), 'email_port': self.data.get('email_port'), 'email_host_user': self.data.get('email_host_user'), 'email_host_password': self.data.get('email_host_password'), 'email_use_tls': self.data.get('email_use_tls'), 'email_use_ssl': self.data.get('email_use_ssl'), 'from_email': self.data.get('from_email') } ================================================ FILE: apps/system_manage/serializers/resource_mapping_serializers.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_user_resource_permission.py @date:2025/4/28 17:17 @desc: """ import json import os from django.db import models from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_page_search, get_dynamics_model from common.result import Page from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR from system_manage.models.resource_mapping import ResourceMapping class ResourceMappingSerializer(serializers.Serializer): resource = serializers.CharField(required=True, label=_('resource')) resource_id = serializers.UUIDField(required=True, label=_('resource Id')) resource_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('resource Name')) source_type = serializers.ListField( label=_('source Type'), child=serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source Type'))) user_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('creator')) workspace_ids = serializers.CharField(required=False, label=_('workspace_ids')) def get_query_set(self): queryset = QuerySet(model=get_dynamics_model({ 'sdc.name': models.CharField(), 'target_id': models.CharField(), "target_type": models.CharField(), "u.username": models.CharField(), 'rm.source_type': models.CharField(), 'workspace_id': models.CharField(), })) queryset = queryset.filter(target_id=self.data.get('resource_id'), target_type=self.data.get('resource')) if self.data.get('resource_name'): queryset = queryset.filter(**{'sdc.name__icontains': self.data.get('resource_name')}) if self.data.get('user_name'): queryset = queryset.filter(**{'u.username__icontains': self.data.get('user_name')}) if self.data.get("source_type"): queryset = queryset.filter(**{'rm.source_type__in': self.data.get('source_type')}) if self.data.get('workspace_ids') is not None and len(self.data.get('workspace_ids')) > 0: workspace_ids = json.loads(self.data.get('workspace_ids')) queryset = queryset.filter(**{'workspace_id__in': workspace_ids}) return queryset @staticmethod def is_x_pack_ee(): workspace_model = DatabaseModelManage.get_model("workspace_model") return workspace_model is not None def page(self, current_page, page_size): is_x_pack_ee = self.is_x_pack_ee() return native_page_search(current_page, page_size, self.get_query_set(), get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', 'list_resource_mapping_ee.sql' if is_x_pack_ee else 'list_resource_mapping.sql')), with_table_name=False) def get_resource_count(self, result_list): """ 获取资源映射计数 """ if not result_list: return result_list is_paginated = isinstance(result_list, Page) data_to_process = result_list.get('records') if is_paginated else result_list if isinstance(data_to_process, list) and data_to_process: # 提取ID列表,确保每个项目都是字典且包含'id'键 ids = [item['id'] for item in data_to_process if isinstance(item, dict) and 'id' in item and item['id']] if ids: # 只有在ids非空时才执行查询 mapping_counts = ResourceMapping.objects.filter( target_id__in=ids ).values('target_id').annotate( count=models.Count('id') ) # 构建目标ID到计数的映射 count_dict = {str(item['target_id']): item['count'] for item in mapping_counts} # 为每个结果项添加资源计数 for model in data_to_process: if isinstance(model, dict) and 'id' in model: model_id = str(model['id']) model['resource_count'] = count_dict.get(model_id, 0) return result_list ================================================ FILE: apps/system_manage/serializers/system.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: system.py @date:2025/6/4 16:01 @desc: """ import os from django.db import models from rest_framework import serializers from django.core.cache import cache from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.utils.rsa_util import get_key_pair_by_sql from maxkb import settings from system_manage.models import SystemSetting class SettingType(models.CharField): # Community Edition CE = "CE", "社区" # Enterprise Edition PE = "PE", "专业版" # Professional Edition EE = "EE", '企业版' class SystemProfileResponseSerializer(serializers.Serializer): version = serializers.CharField(required=True, label="version") edition = serializers.CharField(required=True, label="edition") license_is_valid = serializers.BooleanField(required=True, label="License is valid") class SystemProfileSerializer(serializers.Serializer): @staticmethod def profile(): version = os.environ.get('MAXKB_VERSION') license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False) return {'version': version, 'edition': settings.edition, 'license_is_valid': license_is_valid() if license_is_valid() is not None else False, 'ras': get_key_pair_by_sql().get('key')} ================================================ FILE: apps/system_manage/serializers/user_resource_permission.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_user_resource_permission.py @date:2025/4/28 17:17 @desc: """ import json import os from django.contrib.postgres.fields import ArrayField from django.core.cache import cache from django.db import models from django.db.models import QuerySet, Q, TextField from django.db.models.functions import Cast from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application from common.constants.cache_version import Cache_Version from common.constants.permission_constants import get_default_workspace_user_role_mapping_list, RoleConstants, \ ResourcePermission, ResourcePermissionRole, ResourceAuthType from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search, native_page_search, get_dynamics_model from common.db.sql_execute import select_list from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from knowledge.models import Knowledge from maxkb.conf import PROJECT_DIR from maxkb.settings import edition from models_provider.models import Model from system_manage.models import WorkspaceUserResourcePermission from tools.models import Tool from users.serializers.user import is_workspace_manage class PermissionSerializer(serializers.Serializer): VIEW = serializers.BooleanField(required=True, label="可读") MANAGE = serializers.BooleanField(required=True, label="管理") ROLE = serializers.BooleanField(required=True, label="跟随角色") class UserResourcePermissionItemResponse(serializers.Serializer): id = serializers.UUIDField(required=True, label="主键id") name = serializers.CharField(required=True, label="资源名称") auth_target_type = serializers.CharField(required=True, label="授权资源") user_id = serializers.UUIDField(required=True, label="用户id") icon = serializers.CharField(required=True, label="资源图标") auth_type = serializers.CharField(required=True, label="授权类型") permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) class UserResourcePermissionResponse(serializers.Serializer): KNOWLEDGE = UserResourcePermissionItemResponse(many=True) class UpdateTeamMemberItemPermissionSerializer(serializers.Serializer): target_id = serializers.CharField(required=True, label=_('target id')) permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) class UpdateUserResourcePermissionRequest(serializers.Serializer): user_resource_permission_list = UpdateTeamMemberItemPermissionSerializer(required=True, many=True) def is_valid(self, *, auth_target_type=None, workspace_id=None, raise_exception=False): super().is_valid(raise_exception=True) user_resource_permission_list = [{'target_id': urp.get('target_id'), 'auth_target_type': auth_target_type} for urp in self.data.get("user_resource_permission_list")] illegal_target_id_list = select_list( get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', 'check_member_permission_target_exists.sql')), [json.dumps(user_resource_permission_list), workspace_id, workspace_id, workspace_id, workspace_id, workspace_id, workspace_id, workspace_id]) if illegal_target_id_list is not None and len(illegal_target_id_list) > 0: raise AppApiException(500, _('Non-existent id')+'[' + str(illegal_target_id_list) + ']') m_map = { "KNOWLEDGE": Knowledge, 'TOOL': Tool, 'MODEL': Model, 'APPLICATION': Application, } sql_map = { "KNOWLEDGE": 'get_knowledge_user_resource_permission.sql', 'TOOL': 'get_tool_user_resource_permission.sql', 'MODEL': 'get_model_user_resource_permission.sql', 'APPLICATION': 'get_application_user_resource_permission.sql' } class UserResourcePermissionUserListRequest(serializers.Serializer): name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('resource name')) permission = serializers.MultipleChoiceField(required=False, allow_null=True, allow_blank=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) class UserResourcePermissionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.CharField(required=True, label=_('user id')) auth_target_type = serializers.CharField(required=True, label=_('resource')) def get_queryset(self, instance): resource_query_set = QuerySet( model=get_dynamics_model({ 'name': models.CharField(), "permission": models.CharField(), })) name = instance.get('name') permission = instance.get('permission') query_p_list = [None if p == "NOT_AUTH" else p for p in permission] if name: resource_query_set = resource_query_set.filter(name__contains=name) if permission: if all([p is None for p in query_p_list]): resource_query_set = resource_query_set.filter(permission=None) else: if any([p is None for p in query_p_list]): resource_query_set = resource_query_set.filter( Q(permission__in=query_p_list) | Q(permission=None)) else: resource_query_set = resource_query_set.filter( permission__in=query_p_list) return { 'query_set': QuerySet(m_map.get(self.data.get('auth_target_type'))).filter( workspace_id=self.data.get('workspace_id')), 'folder_query_set': QuerySet(m_map.get(self.data.get('auth_target_type'))).filter( workspace_id=self.data.get('workspace_id')), 'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter( workspace_id=self.data.get('workspace_id'), user=self.data.get('user_id'), auth_target_type=self.data.get('auth_target_type')), 'resource_query_set': resource_query_set } def is_auth(self, resource_id: str): self.is_valid(raise_exception=True) auth_target_type = self.data.get('auth_target_type') workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') workspace_manage = is_workspace_manage(user_id, workspace_id) if workspace_manage: return True wurp = QuerySet(WorkspaceUserResourcePermission).filter(auth_target_type=auth_target_type, workspace_id=workspace_id, user=user_id, target=resource_id).first() if wurp is None: return False workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") if wurp.auth_type == ResourceAuthType.ROLE.value: if workspace_user_role_mapping_model and role_permission_mapping_model: inner = QuerySet(workspace_user_role_mapping_model).filter(workspace_id=workspace_id, user_id=user_id) return QuerySet(role_permission_mapping_model).filter(role_id__in=inner, permission_id=( auth_target_type + ':READ')).exists() else: return False else: return wurp.permission_list.__contains__(ResourcePermission.VIEW.value) def auth_resource_batch(self, resource_id_list: list): self.is_valid(raise_exception=True) auth_target_type = self.data.get('auth_target_type') workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') wurp = QuerySet(WorkspaceUserResourcePermission).filter(auth_target_type=auth_target_type, workspace_id=workspace_id, user_id=user_id).first() auth_type = wurp.auth_type if wurp else ( ResourceAuthType.RESOURCE_PERMISSION_GROUP if edition == 'CE' else ResourceAuthType.ROLE) workspace_user_resource_permission = [WorkspaceUserResourcePermission( target=resource_id, auth_target_type=auth_target_type, permission_list=[ResourcePermission.VIEW, ResourcePermission.MANAGE] if auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP else [ ResourcePermissionRole.ROLE], workspace_id=workspace_id, user_id=user_id, auth_type=auth_type ) for resource_id in resource_id_list] QuerySet(WorkspaceUserResourcePermission).bulk_create(workspace_user_resource_permission) # 刷新缓存 version = Cache_Version.PERMISSION_LIST.get_version() key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id) cache.delete(key, version=version) return True def auth_resource(self, resource_id: str, is_folder=False): self.is_valid(raise_exception=True) auth_target_type = self.data.get('auth_target_type') workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') WorkspaceUserResourcePermission( target=resource_id, auth_target_type=auth_target_type, permission_list=[ResourcePermission.VIEW, ResourcePermission.MANAGE], workspace_id=workspace_id, user_id=user_id, auth_type=ResourceAuthType.RESOURCE_PERMISSION_GROUP ).save() # 刷新缓存 version = Cache_Version.PERMISSION_LIST.get_version() key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id) cache.delete(key, version=version) return True def list(self, instance, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) UserResourcePermissionUserListRequest(data=instance).is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") user_id = self.data.get("user_id") # 用户权限列表 user_resource_permission_list = native_search(self.get_queryset(instance), get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', sql_map.get(self.data.get('auth_target_type'))))) return [{**user_resource_permission} for user_resource_permission in user_resource_permission_list] def page(self, instance, current_page: int, page_size: int, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) UserResourcePermissionUserListRequest(data=instance).is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") user_id = self.data.get("user_id") # 用户对应的资源权限分页列表 user_resource_permission_page_list = native_page_search(current_page, page_size, self.get_queryset(instance), get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', sql_map.get( self.data.get('auth_target_type'))) )) return user_resource_permission_page_list def edit(self, instance, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) UpdateUserResourcePermissionRequest(data={'user_resource_permission_list': instance}).is_valid( raise_exception=True, auth_target_type=self.data.get( 'auth_target_type'), workspace_id=self.data.get('workspace_id')) workspace_id = self.data.get("workspace_id") user_id = self.data.get("user_id") update_list = [] save_list = [] targets = [item['target_id'] for item in instance] QuerySet(WorkspaceUserResourcePermission).filter( workspace_id=workspace_id, user_id=user_id, auth_target_type=self.data.get('auth_target_type'), target__in=targets ).delete() workspace_user_resource_permission_exist_list = [] for user_resource_permission in instance: permission = user_resource_permission['permission'] auth_type, permission_list = permission_map[permission] exist_list = [user_resource_permission_exist for user_resource_permission_exist in workspace_user_resource_permission_exist_list if user_resource_permission.get('target_id') == str(user_resource_permission_exist.target)] if len(exist_list) > 0: exist_list[0].permission_list = [key for key in user_resource_permission.get('permission').keys() if user_resource_permission.get('permission').get(key)] exist_list[0].auth_type = user_resource_permission.get('auth_type') update_list.append(exist_list[0]) else: save_list.append(WorkspaceUserResourcePermission(target=user_resource_permission.get('target_id'), auth_target_type=self.data.get('auth_target_type'), permission_list=permission_list, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type)) # 批量更新 QuerySet(WorkspaceUserResourcePermission).bulk_update(update_list, ['permission_list', 'auth_type']) if len( update_list) > 0 else None # 批量插入 QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list) if len(save_list) > 0 else None version = Cache_Version.PERMISSION_LIST.get_version() key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id) cache.delete(key, version=version) return instance class ResourceUserPermissionUserListRequest(serializers.Serializer): nick_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('workspace id')) username = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('workspace id')) permission = serializers.MultipleChoiceField(required=False, allow_null=True, allow_blank=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) class ResourceUserPermissionEditRequest(serializers.Serializer): user_id = serializers.CharField(required=True, label=_('workspace id')) permission = serializers.ChoiceField(required=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'], label=_('permission')) permission_map = { "ROLE": ("ROLE", ["ROLE"]), "MANAGE": ("RESOURCE_PERMISSION_GROUP", ["MANAGE", "VIEW"]), "VIEW": ("RESOURCE_PERMISSION_GROUP", ["VIEW"]), "NOT_AUTH": ("RESOURCE_PERMISSION_GROUP", []), } class ResourceUserPermissionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) target = serializers.CharField(required=True, label=_('resource id')) auth_target_type = serializers.CharField(required=True, label=_('resource')) users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission')) RESOURCE_MODEL_MAP = { 'APPLICATION': Application, 'KNOWLEDGE': Knowledge, 'TOOL': Tool } def get_queryset(self, instance, is_x_pack_ee: bool): user_query_set = QuerySet(model=get_dynamics_model({ 'nick_name': models.CharField(), 'username': models.CharField(), "permission": models.CharField(), "u.id": models.UUIDField(), "role": models.CharField(), "role_setting.type": models.CharField(), "user_role_relation.workspace_id": models.CharField(), 'tmp.type_list': ArrayField(models.CharField()), 'tmp.role_name_list_str': models.CharField() })) nick_name = instance.get('nick_name') username = instance.get('username') role_name = instance.get('role') permission = instance.get('permission') query_p_list = [None if p == "NOT_AUTH" else p for p in permission] workspace_user_resource_permission_query_set = QuerySet(WorkspaceUserResourcePermission).filter( workspace_id=self.data.get('workspace_id'), auth_target_type=self.data.get('auth_target_type'), target=self.data.get('target')) if nick_name: user_query_set = user_query_set.filter(nick_name__contains=nick_name) if username: user_query_set = user_query_set.filter(username__contains=username) if permission: if all([p is None for p in query_p_list]): user_query_set = user_query_set.filter( permission=None) else: if any([p is None for p in query_p_list]): user_query_set = user_query_set.filter( Q(permission__in=query_p_list) | Q(permission=None)) else: user_query_set = user_query_set.filter( permission__in=query_p_list) workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") if workspace_user_role_mapping_model: user_query_set = user_query_set.filter( **{"u.id__in": QuerySet(workspace_user_role_mapping_model).filter( workspace_id=self.data.get('workspace_id')).values("user_id")}) if is_x_pack_ee: user_query_set = user_query_set.filter(**{ "tmp.type_list__contains": ["USER"] }) role_name_and_type_query_set = QuerySet(model=get_dynamics_model({ 'user_role_relation.workspace_id': models.CharField(), 'role_setting.type': models.CharField(), })).filter(**{ "user_role_relation.workspace_id": self.data.get('workspace_id'), "role_setting.type": "USER", }) if role_name: user_query_set = user_query_set.filter( **{'tmp.role_name_list_str__icontains': str(role_name)} ) return { 'workspace_user_resource_permission_query_set': workspace_user_resource_permission_query_set, 'user_query_set': user_query_set, 'role_name_and_type_query_set': role_name_and_type_query_set } else: user_query_set = user_query_set.filter( **{'role': "USER"}) return { 'workspace_user_resource_permission_query_set': workspace_user_resource_permission_query_set, 'user_query_set': user_query_set } def list(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ResourceUserPermissionUserListRequest(data=instance).is_valid(raise_exception=True) is_x_pack_ee = self.is_x_pack_ee() # 资源的用户授权列表 resource_user_permission_list = native_search(self.get_queryset(instance, is_x_pack_ee), get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', ('get_resource_user_permission_detail_ee.sql' if is_x_pack_ee else 'get_resource_user_permission_detail.sql') ) )) return resource_user_permission_list @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def page(self, instance, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ResourceUserPermissionUserListRequest(data=instance).is_valid(raise_exception=True) # 分页列表 is_x_pack_ee = self.is_x_pack_ee() resource_user_permission_page_list = native_page_search(current_page, page_size, self.get_queryset(instance, is_x_pack_ee), get_file_content( os.path.join(PROJECT_DIR, "apps", "system_manage", 'sql', ( 'get_resource_user_permission_detail_ee.sql' if is_x_pack_ee else 'get_resource_user_permission_detail.sql') ) )) return resource_user_permission_page_list def get_has_manage_permission_resource_under_folders(self, current_user_id, folder_ids): workspace_id = self.data.get("workspace_id") auth_target_type = self.data.get("auth_target_type") workspace_manage = is_workspace_manage(current_user_id, workspace_id) resource_model = self.RESOURCE_MODEL_MAP[auth_target_type] from folders.serializers.folder import has_exact_permission_by_role permission_id = f"{auth_target_type}:READ+AUTH" if workspace_manage: role_type = RoleConstants.WORKSPACE_MANAGE.value.__str__() has_user_role_exact_permission = has_exact_permission_by_role(current_user_id, workspace_id, permission_id,role_type) if has_user_role_exact_permission: current_user_managed_resources_ids = QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate( id_str=Cast('id', TextField()) ).values_list("id_str", flat=True) else: current_user_managed_resources_ids = [] else: role_type = RoleConstants.USER.value.__str__() has_user_role_exact_permission = has_exact_permission_by_role(current_user_id, workspace_id, permission_id,role_type) permission_list = ['MANAGE'] if has_user_role_exact_permission: permission_list = ['MANAGE','ROLE'] current_user_managed_resources_ids = QuerySet(WorkspaceUserResourcePermission).filter( workspace_id=workspace_id, user_id=current_user_id, auth_target_type=auth_target_type, target__in=QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate( id_str=Cast('id', TextField()) ).values_list("id_str", flat=True), permission_list__overlap= permission_list).values_list('target', flat=True) return current_user_managed_resources_ids def edit(self, instance, with_valid=True, current_user_id=None): if with_valid: self.is_valid(raise_exception=True) ResourceUserPermissionEditRequest(data=instance, many=True).is_valid( raise_exception=True) workspace_id = self.data.get("workspace_id") target = self.data.get("target") auth_target_type = self.data.get("auth_target_type") users_permission = instance users_id = [item["user_id"] for item in users_permission] include_children = users_permission[0].get('include_children') folder_ids = users_permission[0].get('folder_ids') # 删除已存在的对应的用户在该资源下的权限 if include_children: managed_resource_ids = list( self.get_has_manage_permission_resource_under_folders(current_user_id, folder_ids,)) + folder_ids else: managed_resource_ids = [target] QuerySet(WorkspaceUserResourcePermission).filter( workspace_id=workspace_id, target__in=managed_resource_ids, auth_target_type=auth_target_type, user_id__in=users_id ).delete() save_list = [ WorkspaceUserResourcePermission( target=resource_id, auth_target_type=auth_target_type, workspace_id=workspace_id, auth_type=permission_map[item['permission']][0], user_id=item["user_id"], permission_list=permission_map[item['permission']][1] ) for resource_id in managed_resource_ids for item in users_permission ] if save_list: QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list) version = Cache_Version.PERMISSION_LIST.get_version() for user_id in users_id: key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id) cache.delete(key, version=version) return instance ================================================ FILE: apps/system_manage/serializers/valid_serializers.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: valid_serializers.py @date:2024/7/8 18:00 @desc: """ import re from django.core import validators from django.core.cache import cache from django.db.models import QuerySet from rest_framework import serializers from application.models import Application from common.constants.cache_version import Cache_Version from common.exception.app_exception import AppApiException from knowledge.models import Knowledge from users.models import User from django.utils.translation import gettext_lazy as _ model_message_dict = { 'dataset': {'model': Knowledge, 'count': 50, 'message': _( 'The community version supports up to 50 knowledge bases. If you need more knowledge bases, please contact us (https://fit2cloud.com/).')}, 'application': {'model': Application, 'count': 5, 'message': _( 'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).')}, 'user': {'model': User, 'count': 2, 'message': _( 'The community version supports up to 2 users. If you need more users, please contact us (https://fit2cloud.com/).')} } class ValidSerializer(serializers.Serializer): valid_type = serializers.CharField(required=True, label=_('type'), validators=[ validators.RegexValidator(regex=re.compile("^application|knowledge|user$"), message="类型只支持:application|knowledge|user", code=500) ]) valid_count = serializers.IntegerField(required=True, label=_('check quantity')) def valid(self, is_valid=True): if is_valid: self.is_valid(raise_exception=True) model_value = model_message_dict.get(self.data.get('valid_type')) license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'), version=Cache_Version.SYSTEM.get_version()) is_license_valid = license_is_valid if license_is_valid is not None else False if not is_license_valid: if self.data.get('valid_count') != model_value.get('count'): raise AppApiException(400, model_value.get('message')) if QuerySet( model_value.get('model')).count() >= model_value.get('count'): raise AppApiException(400, model_value.get('message')) return True ================================================ FILE: apps/system_manage/sql/check_member_permission_target_exists.sql ================================================ SELECT static_temp."target_id"::text FROM (SELECT * FROM json_to_recordset( %s ) AS x(target_id text, auth_target_type text)) static_temp LEFT JOIN (SELECT id::text AS id, auth_target_type FROM (SELECT "id"::text, 'KNOWLEDGE' AS "auth_target_type" FROM knowledge WHERE workspace_id = %s UNION SELECT "id"::text, 'KNOWLEDGE' AS "auth_target_type" FROM knowledge_folder WHERE workspace_id = %s UNION SELECT "id"::text, 'APPLICATION' AS "auth_target_type" FROM application WHERE workspace_id = %s UNION SELECT "id"::text, 'APPLICATION' AS "auth_target_type" FROM application_folder WHERE workspace_id = %s UNION SELECT "id"::text, 'MODEL' AS "auth_target_type" FROM model WHERE workspace_id = %s UNION SELECT "id"::text, 'TOOL' AS "auth_target_type" FROM tool WHERE workspace_id = %s UNION SELECT "id"::text, 'TOOL' AS "auth_target_type" FROM tool_folder WHERE workspace_id = %s ) "union_temp") "app_and_knowledge_temp" ON "app_and_knowledge_temp"."id" = static_temp."target_id" and app_and_knowledge_temp."auth_target_type" = static_temp."auth_target_type" WHERE app_and_knowledge_temp.id is NULL; ================================================ FILE: apps/system_manage/sql/get_application_user_resource_permission.sql ================================================ SELECT resource_or_folder.*, CASE WHEN wurp.permission IS NULL THEN 'NOT_AUTH' ELSE wurp.permission END FROM ( SELECT id::text, "name", 'APPLICATION' AS "auth_target_type", 'application' AS "resource_type", user_id, workspace_id, icon, folder_id, create_time FROM application ${query_set} UNION SELECT application_folder."id"::text, application_folder."name", 'APPLICATION' AS "auth_target_type", 'folder' AS "resource_type", application_folder."user_id", application_folder."workspace_id", NULL AS "icon", application_folder."parent_id" AS "folder_id", application_folder."create_time" FROM application_folder ${folder_query_set} ) resource_or_folder LEFT JOIN ( SELECT target, CASE WHEN auth_type = 'ROLE' AND 'ROLE' = ANY (permission_list) THEN 'ROLE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'MANAGE' = ANY (permission_list) THEN 'MANAGE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'VIEW' = ANY (permission_list) THEN 'VIEW' ELSE NULL END AS permission FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON wurp.target::text = resource_or_folder.id ${resource_query_set} ORDER BY resource_or_folder.create_time DESC ================================================ FILE: apps/system_manage/sql/get_knowledge_user_resource_permission.sql ================================================ SELECT resource_or_folder.*, CASE WHEN wurp.permission IS NULL THEN 'NOT_AUTH' ELSE wurp.permission END FROM ( SELECT id::text, "name", 'KNOWLEDGE' AS "auth_target_type", 'knowledge' AS "resource_type", user_id, workspace_id, "type"::varchar AS "icon", folder_id, create_time FROM knowledge ${query_set} UNION SELECT knowledge_folder."id"::text, knowledge_folder."name", 'KNOWLEDGE' AS "auth_target_type", 'folder' AS "resource_type", knowledge_folder."user_id", knowledge_folder."workspace_id", NULL AS "icon", knowledge_folder."parent_id" AS "folder_id", knowledge_folder."create_time" FROM knowledge_folder ${folder_query_set} ) resource_or_folder LEFT JOIN ( SELECT target, CASE WHEN auth_type = 'ROLE' AND 'ROLE' = ANY(permission_list) THEN 'ROLE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'VIEW' = ANY(permission_list) THEN 'VIEW' ELSE null END AS permission FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON wurp.target::text = resource_or_folder.id ${resource_query_set} ORDER BY resource_or_folder.create_time DESC ================================================ FILE: apps/system_manage/sql/get_model_user_resource_permission.sql ================================================ SELECT resource_or_folder.*, CASE WHEN wurp."permission" is null then 'NOT_AUTH' ELSE wurp."permission" END FROM ( SELECT "id"::text, "name", 'MODEL' AS "auth_target_type", 'model' AS "resource_type", user_id, workspace_id, provider as icon, 'default' as folder_id, create_time FROM model ${query_set} UNION SELECT "id"::text, "name", 'MODEL' AS "auth_target_type", 'folder' AS "resource_type", user_id, workspace_id, provider as icon, 'default' as folder_id, create_time FROM model ${folder_query_set} AND 1=0 ) resource_or_folder LEFT JOIN ( SELECT target, CASE WHEN auth_type = 'ROLE' AND 'ROLE' = ANY(permission_list) THEN 'ROLE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'VIEW' = ANY(permission_list) THEN 'VIEW' ELSE null END AS permission FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON wurp.target = resource_or_folder."id" ${resource_query_set} ORDER BY resource_or_folder.create_time DESC ================================================ FILE: apps/system_manage/sql/get_resource_user_permission_detail.sql ================================================ SELECT u.id, u.nick_name, u.username, case when wurp."permission" is null then 'NOT_AUTH' else wurp."permission" end FROM public."user" u LEFT JOIN ( SELECT user_id , (case when auth_type = 'ROLE' and 'ROLE' = any( permission_list) then 'ROLE' when auth_type = 'RESOURCE_PERMISSION_GROUP' and 'MANAGE'= any(permission_list) then 'MANAGE' when auth_type = 'RESOURCE_PERMISSION_GROUP' and 'VIEW' = any( permission_list) then 'VIEW' else null end) as "permission" FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON u.id = wurp.user_id ${user_query_set} ================================================ FILE: apps/system_manage/sql/get_resource_user_permission_detail_ee.sql ================================================ SELECT DISTINCT u.id, u.nick_name, u.username, tmp.role_name_list AS role_name, CASE WHEN wurp."permission" IS NULL THEN 'NOT_AUTH' ELSE wurp."permission" END AS permission FROM public."user" u LEFT JOIN ( SELECT user_id, CASE WHEN auth_type = 'ROLE' AND 'ROLE' = ANY(permission_list) THEN 'ROLE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'VIEW' = ANY(permission_list) THEN 'VIEW' ELSE NULL END AS "permission" FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON u.id = wurp.user_id LEFT JOIN ( SELECT ARRAY_AGG(role_setting.role_name) AS role_name_list, ARRAY_AGG(role_setting.role_name)::text AS role_name_list_str, ARRAY_AGG(role_setting.type) AS type_list, user_role_relation.user_id FROM user_role_relation user_role_relation LEFT JOIN role_setting role_setting ON role_setting.id = user_role_relation.role_id ${role_name_and_type_query_set} GROUP BY user_role_relation.user_id) tmp ON u.id = tmp.user_id ${user_query_set} ================================================ FILE: apps/system_manage/sql/get_tool_user_resource_permission.sql ================================================ SELECT resource_or_folder.*, CASE WHEN wurp."permission" IS NULL THEN 'NOT_AUTH' ELSE wurp."permission" END FROM ( SELECT "id"::text, "name", 'TOOL' AS "auth_target_type", 'tool' AS "resource_type", user_id, workspace_id, icon, folder_id, tool_type, create_time FROM tool ${query_set} UNION SELECT tool_folder."id"::text, tool_folder."name", 'TOOL' AS "auth_target_type", 'folder' AS "resource_type", tool_folder."user_id", tool_folder."workspace_id", NULL AS "icon", tool_folder."parent_id" AS "folder_id", NULL AS "tool_type", tool_folder."create_time" FROM tool_folder ${folder_query_set} ) resource_or_folder LEFT JOIN ( SELECT target, CASE WHEN auth_type = 'ROLE' AND 'ROLE' = ANY(permission_list) THEN 'ROLE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE' WHEN auth_type = 'RESOURCE_PERMISSION_GROUP' AND 'VIEW' = ANY(permission_list) THEN 'VIEW' ELSE null END AS permission FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} ) wurp ON wurp.target::text = resource_or_folder."id" ${resource_query_set} ORDER BY resource_or_folder.create_time DESC ================================================ FILE: apps/system_manage/sql/get_user_resource_permission.sql ================================================ SELECT app_or_knowledge.*, COALESCE(workspace_user_resource_permission.permission_list,'{}')::varchar[] as permission_list, COALESCE(workspace_user_resource_permission.auth_type,'ROLE') as auth_type FROM (SELECT "id", "name", 'KNOWLEDGE' AS "auth_target_type", user_id, workspace_id, "type"::varchar AS "icon", folder_id FROM knowledge ${knowledge_query_set} UNION SELECT "id", "name", 'APPLICATION' AS "auth_target_type", user_id, workspace_id, icon, folder_id FROM application ${application_query_set} UNION SELECT "id", "name", 'TOOL' AS "auth_target_type", user_id, workspace_id, icon, folder_id FROM tool ${tool_query_set} UNION SELECT "id", "name", 'MODEL' AS "auth_target_type", user_id, workspace_id, provider as icon, 'default' as folder_id FROM model ${model_query_set} ) app_or_knowledge LEFT JOIN (SELECT * FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set}) workspace_user_resource_permission ON workspace_user_resource_permission.target = app_or_knowledge."id"; ================================================ FILE: apps/system_manage/sql/list_resource_mapping.sql ================================================ WITH source_data_cte AS (SELECT 'APPLICATION' as source_type, id, "name", "desc", "user_id", "workspace_id", "icon", "type", "folder_id" FROM application UNION ALL SELECT 'KNOWLEDGE' as source_type, id, "name", "desc", "user_id", "workspace_id", "type"::text as "icon" , "type"::text as "type", "folder_id" FROM knowledge) SELECT rm.*, sdc.*, u.username as username FROM resource_mapping rm LEFT JOIN source_data_cte sdc ON rm.source_type = sdc.source_type AND rm.source_id::uuid = sdc.id LEFT JOIN "public"."user" u on u.id = sdc.user_id ================================================ FILE: apps/system_manage/sql/list_resource_mapping_ee.sql ================================================ WITH source_data_cte AS (SELECT 'APPLICATION' as source_type, id, "name", "desc", "user_id", "workspace_id", "icon", "type", "folder_id" FROM application UNION ALL SELECT 'KNOWLEDGE' as source_type, id, "name", "desc", "user_id", "workspace_id", "type"::text as "icon" , "type"::text as "type", "folder_id" FROM knowledge) SELECT rm.*, sdc.*, u.username as username, w.name as workspace_name FROM resource_mapping rm LEFT JOIN source_data_cte sdc ON rm.source_type = sdc.source_type AND rm.source_id::uuid = sdc.id LEFT JOIN "public"."user" u on u.id = sdc.user_id LEFT JOIN "public"."workspace" w on w.id = sdc.workspace_id ================================================ FILE: apps/system_manage/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/system_manage/urls.py ================================================ from django.urls import path from . import views app_name = "system_manage" # @formatter:off urlpatterns = [ path('workspace//user_resource_permission/user//resource/', views.WorkSpaceUserResourcePermissionView.as_view()), path('workspace//user_resource_permission/user//resource///', views.WorkSpaceUserResourcePermissionView.Page.as_view()), path('workspace//resource_user_permission/resource//resource/', views.WorkspaceResourceUserPermissionView.as_view()), path('workspace//resource_user_permission/resource//resource///', views.WorkspaceResourceUserPermissionView.Page.as_view()), path('workspace//resource_mapping////', views.ResourceMappingView.as_view()), path('email_setting', views.SystemSetting.Email.as_view()), path('profile', views.SystemProfile.as_view()), path('valid//', views.Valid.as_view()) ] ================================================ FILE: apps/system_manage/views/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/16 19:07 @desc: """ from .user_resource_permission import * from .email_setting import * from .system_profile import * from .valid import * from .resource_mapping import * ================================================ FILE: apps/system_manage/views/email_setting.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: system_setting.py @date:2024/3/19 16:01 @desc: """ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants from django.utils.translation import gettext_lazy as _ from common.log.log import log from common.result import result from common.utils.common import encryption from models_provider.api.model import DefaultModelResponse from system_manage.api.email_setting import EmailSettingAPI from system_manage.serializers.email_setting import EmailSettingSerializer def encryption_str(_value): if isinstance(_value, str): return encryption(_value) return _value def get_email_details(request): path = request.path body = request.data query = request.query_params email_host_password = body.get('email_host_password', '') return { 'path': path, 'body': {**body, 'email_host_password': encryption_str(email_host_password)}, 'query': query } class SystemSetting(APIView): class Email(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['PUT'], summary=_('Create or update email settings'), description=_('Create or update email settings'), operation_id=_('Create or update email settings'), # type: ignore request=EmailSettingAPI.get_request(), responses=EmailSettingAPI.get_response(), tags=[_('Email Settings')]) # type: ignore @log(menu='Email settings', operate='Create or update email settings', get_details=get_email_details) @has_permissions(PermissionConstants.EMAIL_SETTING_EDIT, RoleConstants.ADMIN) def put(self, request: Request): return result.success( EmailSettingSerializer.Create( data=request.data).update_or_save()) @extend_schema( methods=['POST'], summary=_('Test email settings'), operation_id=_('Test email settings'), # type: ignore request=EmailSettingAPI.get_request(), responses=DefaultModelResponse.get_response(), tags=[_('Email Settings')] # type: ignore ) @has_permissions(PermissionConstants.EMAIL_SETTING_EDIT, RoleConstants.ADMIN) @log(menu='Email settings', operate='Test email settings', get_details=get_email_details ) def post(self, request: Request): return result.success( EmailSettingSerializer.Create( data=request.data).is_valid()) @extend_schema(methods=['GET'], summary=_('Get email settings'), description=_('Get email settings'), operation_id=_('Get email settings'), # type: ignore responses=DefaultModelResponse.get_response(), tags=[_('Email Settings')]) # type: ignore @has_permissions(PermissionConstants.EMAIL_SETTING_READ, RoleConstants.ADMIN) def get(self, request: Request): return result.success( EmailSettingSerializer.one()) ================================================ FILE: apps/system_manage/views/log_management.py ================================================ ================================================ FILE: apps/system_manage/views/resource_mapping.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: resource_mapping.py @date:2025/12/25 15:28 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ViewPermission, \ CompareConstants from system_manage.api.resource_mapping import ResourceMappingAPI from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer class ResourceMappingView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Retrieve the pagination list of resource relationships'), operation_id=_('Retrieve the pagination list of resource relationships'), # type: ignore responses=ResourceMappingAPI.get_response(), parameters=ResourceMappingAPI.get_parameters(), tags=[_('Resources mapping')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.RELATE_VIEW, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"), lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.RELATE_VIEW, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('resource_id')}"), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('resource_id')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, resource: str, resource_id: str, current_page, page_size): return result.success(ResourceMappingSerializer({ 'resource': resource, 'resource_id': resource_id, 'resource_name': request.query_params.get('resource_name'), 'user_name': request.query_params.get('user_name'), 'source_type': request.query_params.getlist('source_type[]'), }).page(current_page, page_size)) ================================================ FILE: apps/system_manage/views/system_profile.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: system_profile.py @date:2025/6/4 15:59 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from system_manage.api.system import SystemProfileAPI from system_manage.serializers.system import SystemProfileSerializer class SystemProfile(APIView): @extend_schema( methods=['GET'], description=_('Get MaxKB related information'), operation_id=_('Get MaxKB related information'), # type: ignore responses=SystemProfileAPI.get_response(), tags=[_('System parameters')] # type: ignore ) def get(self, request: Request): return result.success(SystemProfileSerializer.profile()) ================================================ FILE: apps/system_manage/views/user_resource_permission.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: workspace_user_resource_permission.py @date:2025/4/28 16:38 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import RoleConstants, Permission, Group, Operate, ViewPermission, \ CompareConstants from common.log.log import log from system_manage.api.user_resource_permission import UserResourcePermissionAPI, EditUserResourcePermissionAPI, \ ResourceUserPermissionAPI, ResourceUserPermissionPageAPI, ResourceUserPermissionEditAPI, \ UserResourcePermissionPageAPI from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer, \ ResourceUserPermissionSerializer from users.models import User def get_user_operation_object(user_id): user_model = QuerySet(model=User).filter(id=user_id).first() if user_model is not None: return { "name": user_model.username } return {} class WorkSpaceUserResourcePermissionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Obtain resource authorization list'), operation_id=_('Obtain resource authorization list'), # type: ignore parameters=UserResourcePermissionAPI.get_parameters(), responses=UserResourcePermissionAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'), operate=Operate.READ), RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, user_id: str, resource: str): return result.success(UserResourcePermissionSerializer( data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource} ).list({'name': request.query_params.get('name'), 'permission': request.query_params.getlist('permission[]')}, request.user)) @extend_schema( methods=['PUT'], description=_('Modify the resource authorization list'), operation_id=_('Modify the resource authorization list'), # type: ignore parameters=EditUserResourcePermissionAPI.get_parameters(), request=EditUserResourcePermissionAPI.get_request(), responses=EditUserResourcePermissionAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @log(menu='System', operate='Modify the resource authorization list', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')) ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'), operate=Operate.EDIT), RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def put(self, request: Request, workspace_id: str, user_id: str, resource: str): return result.success(UserResourcePermissionSerializer( data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource} ).edit(request.data, request.user)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Obtain resource authorization list by page'), summary=_('Obtain resource authorization list by page'), operation_id=_('Obtain resource authorization list by page'), # type: ignore request=None, parameters=UserResourcePermissionPageAPI.get_parameters(), responses=UserResourcePermissionPageAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'), operate=Operate.READ), RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, user_id: str, resource: str, current_page: str, page_size: str): return result.success(UserResourcePermissionSerializer( data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource} ).page({'name': request.query_params.get('name'), 'permission': request.query_params.getlist('permission[]')}, current_page, page_size, request.user)) class WorkspaceResourceUserPermissionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get user authorization status of resource'), summary=_('Get user authorization status of resource'), operation_id=_('Get user authorization status of resource'), # type: ignore parameters=ResourceUserPermissionAPI.get_parameters(), responses=ResourceUserPermissionAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"), lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, target: str, resource: str): return result.success(ResourceUserPermissionSerializer( data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''), }).list( {'username': request.query_params.get("username"), 'nick_name': request.query_params.get("nick_name"), 'permission': request.query_params.getlist("permission[]") })) @extend_schema( methods=['PUT'], description=_('Edit user authorization status of resource'), summary=_('Edit user authorization status of resource'), operation_id=_('Edit user authorization status of resource'), # type: ignore parameters=ResourceUserPermissionEditAPI.get_parameters(), request=ResourceUserPermissionEditAPI.get_request(), responses=ResourceUserPermissionEditAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @log(menu='System', operate='Edit user authorization status of resource', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')) ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"), lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def put(self, request: Request, workspace_id: str, target: str, resource: str): return result.success(ResourceUserPermissionSerializer( data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''), }) .edit(instance=request.data, current_user_id=request.user.id)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get user authorization status of resource by page'), summary=_('Get user authorization status of resource by page'), operation_id=_('Get user authorization status of resource by page'), # type: ignore parameters=ResourceUserPermissionPageAPI.get_parameters(), responses=ResourceUserPermissionPageAPI.get_response(), tags=[_('Resources authorization')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"), lambda r, kwargs: Permission(group=Group(kwargs.get('resource')), operate=Operate.AUTH, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, target: str, resource: str, current_page: int, page_size: int): return result.success(ResourceUserPermissionSerializer( data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''), } ).page({'username': request.query_params.get("username"), 'role': request.query_params.get("role"), 'nick_name': request.query_params.get("nick_name"), 'permission': request.query_params.getlist("permission[]")}, current_page, page_size, )) ================================================ FILE: apps/system_manage/views/valid.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎 @file: valid.py @date:2024/7/8 17:50 @desc: """ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from django.utils.translation import gettext_lazy as _ from common.result import result from system_manage.serializers.valid_serializers import ValidSerializer class Valid(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get verification results'), summary=_('Get verification results'), operation_id=_('Get verification results'), # type: ignore tags=[_('Validation')] # type: ignore ) def get(self, request: Request, valid_type: str, valid_count: int): return result.success(ValidSerializer(data={'valid_type': valid_type, 'valid_count': valid_count}).valid()) ================================================ FILE: apps/tools/__init__.py ================================================ ================================================ FILE: apps/tools/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/tools/api/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/tools/api/tool.py ================================================ # coding=utf-8 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, DefaultResultSerializer from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest, ToolDebugRequest, ToolEditRequest, \ PylintInstance, AddInternalToolRequest class ToolCreateResponse(ResultSerializer): def get_data(self): return ToolModelSerializer() class ToolCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return ToolCreateRequest @staticmethod def get_response(): return ToolCreateResponse class ToolReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tool_id", description="工具id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return ToolCreateResponse class ToolEditAPI(ToolReadAPI): @staticmethod def get_request(): return ToolEditRequest class ToolDeleteAPI(ToolReadAPI): @staticmethod def get_response(): return DefaultResultSerializer class ToolTreeReadAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="folder_id", description="文件夹id", type=OpenApiTypes.STR, location='query', required=False, ) ] class ToolDebugApi(APIMixin): @staticmethod def get_request(): return ToolDebugRequest @staticmethod def get_response(): return DefaultResultSerializer class ToolExportAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tool_id", description="工具id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_response(): return DefaultResultSerializer class ToolImportAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return { 'multipart/form-data': { 'type': 'object', 'properties': { 'file': { 'type': 'string', 'format': 'binary' # Tells Swagger it's a file } } } } @staticmethod def get_response(): return DefaultResultSerializer class ToolPageAPI(ToolReadAPI): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description="当前页码", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description="每页大小", type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="folder_id", description="文件夹id", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="user_id", description="创建者id", type=OpenApiTypes.STR, location='query', required=False, ), OpenApiParameter( name="scope", description="工具类型", type=OpenApiTypes.STR, enum=["SHARED", "WORKSPACE"], location='query', required=True, ), OpenApiParameter( name="name", description="工具名称", type=OpenApiTypes.STR, location='query', required=False, ), ] class PylintAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return PylintInstance class EditIconAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tool_id", description="工具id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return { 'multipart/form-data': { 'type': 'object', 'properties': { 'icon': { 'type': 'string', 'format': 'binary' # Tells Swagger it's a file } } } } @staticmethod def get_response(): return DefaultResultSerializer class GetInternalToolAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="name", description="工具名称", type=OpenApiTypes.STR, location='query', required=False, ) ] @staticmethod def get_response(): return DefaultResultSerializer() class AddInternalToolAPI(APIMixin): @staticmethod def get_request(): return AddInternalToolRequest @staticmethod def get_response(): return DefaultResultSerializer @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tool_id", description="工具id", type=OpenApiTypes.STR, location='path', required=True, ), ] ================================================ FILE: apps/tools/api/tool_workflow.py ================================================ # coding=utf-8 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import DefaultResultSerializer from tools.serializers.tool_workflow import ToolWorkflowImportRequest class ToolWorkflowApi(APIMixin): pass class ToolWorkflowVersionApi(APIMixin): pass class ToolWorkflowExportApi(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="tool_id", description="工具id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return DefaultResultSerializer class ToolWorkflowImportApi(APIMixin): @staticmethod def get_parameters(): return ToolWorkflowExportApi.get_parameters() @staticmethod def get_request(): return ToolWorkflowImportRequest @staticmethod def get_response(): return DefaultResultSerializer ================================================ FILE: apps/tools/apps.py ================================================ from django.apps import AppConfig class ToolConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'tools' ================================================ FILE: apps/tools/migrations/0001_initial.py ================================================ # Generated by Django 5.2.3 on 2025-06-23 02:14 import os import django.db.models.deletion import mptt.fields import uuid_utils.compat from django.db import migrations, models from common.utils.common import get_file_content from maxkb.const import PROJECT_DIR from tools.models import ToolFolder def insert_default_data(apps, schema_editor): # 创建一个根模块(没有父节点) ToolFolder.objects.create(id='default', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default') class Migration(migrations.Migration): initial = True dependencies = [ ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='ToolFolder', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')), ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('lft', models.PositiveIntegerField(editable=False)), ('rght', models.PositiveIntegerField(editable=False)), ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), ('level', models.PositiveIntegerField(editable=False)), ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='tools.toolfolder')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'tool_folder', }, ), migrations.RunPython(insert_default_data), migrations.CreateModel( name='Tool', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('name', models.CharField(db_index=True, max_length=64, verbose_name='工具名称')), ('desc', models.CharField(max_length=128, verbose_name='描述')), ('code', models.CharField(max_length=102400, verbose_name='python代码')), ('input_field_list', models.JSONField(default=list, verbose_name='输入字段列表')), ('init_field_list', models.JSONField(default=list, verbose_name='启动字段列表')), ('icon', models.CharField(default='', max_length=256, verbose_name='工具库icon')), ('is_active', models.BooleanField(db_index=True, default=True)), ('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用'), ('INTERNAL', '内置')], db_index=True, default='WORKSPACE', max_length=20, verbose_name='可用范围')), ('tool_type', models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型')), ('template_id', models.UUIDField(db_index=True, default=None, null=True, verbose_name='模版id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('init_params', models.CharField(max_length=102400, null=True, verbose_name='初始化参数')), ('label', models.CharField(db_index=True, max_length=128, null=True, verbose_name='标签')), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ('folder', models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='tools.toolfolder', verbose_name='文件夹id')), ], options={ 'db_table': 'tool', }, ), migrations.RunSQL(get_file_content(os.path.join(PROJECT_DIR, "apps", "tools", 'migrations', 'internal_tool.sql'))) ] ================================================ FILE: apps/tools/migrations/0002_alter_tool_tool_type.py ================================================ # Generated by Django 5.2.4 on 2025-08-11 09:37 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('tools', '0001_initial'), ] operations = [ migrations.AlterField( model_name='tool', name='tool_type', field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('MCP', 'MCP工具')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'), ), ] ================================================ FILE: apps/tools/migrations/0003_alter_tool_template_id.py ================================================ # Generated by Django 5.2.4 on 2025-09-09 04:07 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('tools', '0002_alter_tool_tool_type'), ] operations = [ migrations.AlterField( model_name='tool', name='template_id', field=models.CharField(db_index=True, default=None, max_length=128, null=True, verbose_name='模版id'), ), migrations.AddField( model_name='tool', name='version', field=models.CharField(default=None, max_length=64, null=True, verbose_name='版本号'), ), ] ================================================ FILE: apps/tools/migrations/0004_alter_tool_tool_type.py ================================================ # Generated by Django 5.2.8 on 2025-11-17 07:07 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('tools', '0003_alter_tool_template_id'), ] operations = [ migrations.AlterField( model_name='tool', name='tool_type', field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'), ), ] ================================================ FILE: apps/tools/migrations/0005_taskrecord.py ================================================ # Generated by Django 5.2.9 on 2026-01-29 02:58 import common.encoder.encoder import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models old = [ "https://apps-assets.fit2cloud.com/stable/maxkb/md2docx/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/mcp_output/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/feishubot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/mongo/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/OFD_Parse/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/html_to_pdf/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/feishu_datasource/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/quotation_generation_agent/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/html2pdf/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/sqlbot_ai/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/metaso/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/registry/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/smtp_email/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/wecomrobot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/paperx/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/case_inquire/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/data_analysis_assistant/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/crm_intelligent_search/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/case_collection/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/knowledge_self_assessment/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/minerU_util/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/invoice_recognition/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/fragment/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/contract_review/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/echart_to_svg/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/extract/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/big_order/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/crm_intelligent_recording/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/dingrobot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/timestamp/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/knowledge_workflow/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/household_registration_policy_qa_assistant/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/httputils/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/html_compression/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/crm_customer_entry_ai_agent/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/anspire/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/application_template/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/baidu-translate/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/website_translation/logo.png", ] new = [ "https://apps-assets.fit2cloud.com/stable/maxkb/tool_md2docx/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_mcp_output/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_feishubot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_mongo/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_OFD_Parse/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_html_to_pdf/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/db_feishu_datasource/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_quotation_generation_agent/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_html2pdf/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_sqlbot_ai/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_metaso/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_registry/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_smtp_email/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_wecomrobot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_paperx/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_case_inquire/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_data_analysis_assistant/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_intelligent_search/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_case_collection/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_knowledge_self_assessment/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_minerU_util/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_invoice_recognition/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_fragment/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_contract_review/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_echart_to_svg/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_extract/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_big_order/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_intelligent_recording/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_dingrobot/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_timestamp/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/kbwf_knowledge_workflow/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_household_registration_policy_qa_assistant/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_httputils/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_html_compression/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_customer_entry_ai_agent/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_anspire/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_application_template/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/tool_baidu-translate/logo.png", "https://apps-assets.fit2cloud.com/stable/maxkb/app_website_translation/logo.png", ] def _replace_tool_icons(apps, schema_editor): if len(old) != len(new): raise ValueError("`old` 与 `new` 长度不一致,无法一一替换") Tool = apps.get_model("tools", "Tool") mapping = dict(zip(old, new)) # 逐个 update,避免依赖数据库对 CASE/WHEN 的兼容差异 for old_icon, new_icon in mapping.items(): Tool.objects.filter(icon=old_icon).update(icon=new_icon) class Migration(migrations.Migration): dependencies = [ ('tools', '0004_alter_tool_tool_type'), ] operations = [ migrations.CreateModel( name='ToolRecord', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('KNOWLEDGE', 'Knowledge'), ('TRIGGER', 'Trigger')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')), ('source_id', models.UUIDField(verbose_name='资源id')), ('meta', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder)), ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')), ('run_time', models.FloatField(default=0, verbose_name='运行时长')), ('tool', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ], options={ 'db_table': 'tool_record', }, ), migrations.RunPython(_replace_tool_icons, reverse_code=migrations.RunPython.noop), ] ================================================ FILE: apps/tools/migrations/0006_alter_tool_tool_type.py ================================================ # Generated by Django 5.2.11 on 2026-02-27 02:38 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('tools', '0005_taskrecord'), ] operations = [ migrations.AlterField( model_name='tool', name='tool_type', field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('SKILL', '技能'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'), ), ] ================================================ FILE: apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py ================================================ # Generated by Django 5.2.11 on 2026-03-10 10:46 import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('tools', '0006_alter_tool_tool_type'), ] operations = [ migrations.AlterField( model_name='tool', name='tool_type', field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('SKILL', '技能'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源'), ('WORKFLOW', 'Workflow')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'), ), migrations.CreateModel( name='ToolWorkflow', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('is_publish', models.BooleanField(db_index=True, default=False, verbose_name='是否发布')), ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')), ('tool', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='workflow', to='tools.tool', verbose_name='工具')), ], options={ 'db_table': 'tool_workflow', }, ), migrations.CreateModel( name='ToolWorkflowVersion', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')), ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')), ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')), ('tool', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='tools.tool', verbose_name='工具')), ], options={ 'db_table': 'tool_workflow_version', }, ), ] ================================================ FILE: apps/tools/migrations/__init__.py ================================================ ================================================ FILE: apps/tools/migrations/internal_tool.sql ================================================ INSERT INTO tool (create_time, update_time, id, name, label, "desc", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-10 06:20:35.945414 +00:00', '2025-03-10 09:19:23.608026 +00:00', 'c75cb48e-fd77-11ef-84d2-5618c4394482', '博查搜索', 'web_search', '从博查搜索任何信息和网页URL', e'def bocha_search(query, apikey): import requests import json url = "https://api.bochaai.com/v1/web-search" payload = json.dumps({ "query": query, "summary": True, "count": 8 }) headers = { "Authorization": "Bearer " + apikey, #鉴权参数,示例:Bearer xxxxxx,API KEY请先前往博查AI开放平台(https://open.bochaai.com)> API KEY 管理中获取。 "Content-Type": "application/json" } response = requests.request("POST", url, headers=headers, data=payload) if response.status_code == 200: return response.json() else: raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") return (response.text)', '[{"name": "query", "type": "string", "source": "reference", "is_required": true}]', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "API Key", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "API Key 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "API Key 长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', './tool/bochaai/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default'); INSERT INTO tool (create_time, update_time, id, name, label, "desc", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-02-26 03:36:48.187286 +00:00', '2025-03-11 07:23:46.123972 +00:00', 'e89ad2ae-f3f2-11ef-ad09-0242ac110002', 'Google Search','web_search', 'Google Web Search', e'def google_search(query, apikey, cx): import requests import json url = "https://customsearch.googleapis.com/customsearch/v1" params = { "q": query, "key": apikey, "cx": cx, "num": 10, # 每次最多返回10条 } response = requests.get(url, params=params) if response.status_code == 200: return response.json() else: raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") return (response.text)', '[{"name": "query", "type": "string", "source": "reference", "is_required": true}]', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "API Key", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "API Key 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "API Key 长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "cx", "label": "cx", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "cx 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "cx长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', './tool/google_search/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default'); INSERT INTO tool (create_time, update_time, id, name, label, "desc", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-02-25 07:44:40.141515 +00:00', '2025-03-11 06:33:53.248495 +00:00', '5e912f00-f34c-11ef-8a9c-5618c4394482', 'LangSearch','web_search', e'A Web Search tool supporting natural language search ', e' def langsearch(query, apikey): import json import requests url = "https://api.langsearch.com/v1/web-search" payload = json.dumps({ "query": query, "summary": True, "freshness": "noLimit", "livecrawl": True, "count": 20 }) headers = { "Authorization": apikey, "Content-Type": "application/json" } # key从官网申请 https://langsearch.com/ response = requests.request("POST", url, headers=headers, data=payload) if response.status_code == 200: return response.json() else: raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") return (response.text)', '[{"name": "query", "type": "string", "source": "reference", "is_required": true}]', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "API Key", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "API Key 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "API Key 长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', './tool/langsearch/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default'); INSERT INTO tool (create_time, update_time, id, name, label, "desc", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-17 08:16:32.626245 +00:00', '2025-03-17 08:16:32.626308 +00:00', '22c21b76-0308-11f0-9694-5618c4394482', 'MySQL 查询','database_search', '一个连接MySQL数据库执行SQL查询的工具', e' 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', '[{"name": "sql", "type": "string", "source": "reference", "is_required": true}]', '[{"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "host", "label": "host", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "host 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "host长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 20, "minlength": 1, "show-word-limit": true}, "field": "port", "label": "port", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "port 为必填属性", "required": true}, {"max": 20, "min": 1, "message": "port长度在 1 到 20 个字符", "trigger": "blur"}]}, "default_value": "3306", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "user", "label": "user", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "user 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "user长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "root", "show_default_value": false}, {"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "password", "label": "password", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "password 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "password长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "database", "label": "database", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "database 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "database长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', './tool/mysql/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', null, 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default'); INSERT INTO tool (create_time, update_time, id, name, label, "desc", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-17 07:37:54.620836 +00:00', '2025-03-17 07:37:54.620887 +00:00', 'bd1e8b88-0302-11f0-87bb-5618c4394482', 'PostgreSQL 查询','database_search', '一个连接PostgreSQL数据库执行SQL查询的工具', e' 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()', '[{"name": "query", "type": "string", "source": "reference", "is_required": true}]', '[{"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "host", "label": "host", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "host 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "host长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 20, "minlength": 1, "show-word-limit": true}, "field": "port", "label": "port", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "port 为必填属性", "required": true}, {"max": 20, "min": 1, "message": "port长度在 1 到 20 个字符", "trigger": "blur"}]}, "default_value": "5432", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "user", "label": "user", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "user 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "user长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "root", "show_default_value": false}, {"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "password", "label": "password", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "password 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "password长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "database", "label": "database", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "database 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "database长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', './tool/postgresql/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', null, 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default'); ================================================ FILE: apps/tools/models/__init__.py ================================================ # -*- coding: utf-8 -*- from .tool import * from .tool_workflow import * ================================================ FILE: apps/tools/models/tool.py ================================================ import uuid_utils.compat as uuid from django.db import models from mptt.fields import TreeForeignKey from mptt.models import MPTTModel from common.encoder.encoder import SystemEncoder from common.mixins.app_model_mixin import AppModelMixin from knowledge.models.knowledge_action import State from users.models import User class ToolFolder(MPTTModel, AppModelMixin): id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") name = models.CharField(max_length=64, verbose_name="文件夹名称", db_index=True) desc = models.CharField(max_length=200, null=True, blank=True, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children') class Meta: db_table = "tool_folder" class MPTTMeta: order_insertion_by = ['name'] class ToolScope(models.TextChoices): SHARED = "SHARED", '共享' WORKSPACE = "WORKSPACE", "工作空间可用" INTERNAL = "INTERNAL", '内置' class ToolType(models.TextChoices): INTERNAL = "INTERNAL", '内置' CUSTOM = "CUSTOM", "自定义" SKILL = "SKILL", "技能" MCP = "MCP", "MCP工具" DATA_SOURCE = "DATA_SOURCE", "数据源" WORKFLOW = "WORKFLOW" class ToolTaskTypeChoices(models.TextChoices): APPLICATION = 'APPLICATION' KNOWLEDGE = 'KNOWLEDGE' TOOL = 'TOOL' TRIGGER = 'TRIGGER' class Tool(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) name = models.CharField(max_length=64, verbose_name="工具名称", db_index=True) desc = models.CharField(max_length=128, verbose_name="描述") code = models.CharField(max_length=102400, verbose_name="python代码") input_field_list = models.JSONField(verbose_name="输入字段列表", default=list) init_field_list = models.JSONField(verbose_name="启动字段列表", default=list) icon = models.CharField(max_length=256, verbose_name="工具库icon", default="") is_active = models.BooleanField(default=True, db_index=True) scope = models.CharField(max_length=20, verbose_name='可用范围', choices=ToolScope.choices, default=ToolScope.WORKSPACE, db_index=True) tool_type = models.CharField(max_length=20, verbose_name='工具类型', choices=ToolType.choices, default=ToolType.CUSTOM, db_index=True) template_id = models.CharField(max_length=128, verbose_name="模版id", null=True, default=None, db_index=True) folder = models.ForeignKey(ToolFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='default') workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True) label = models.CharField(max_length=128, verbose_name="标签", null=True, db_index=True) version = models.CharField(max_length=64, verbose_name="版本号", null=True, default=None) class Meta: db_table = "tool" class ToolRecord(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") tool = models.ForeignKey(Tool, on_delete=models.SET_NULL, null=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) source_type = models.CharField(verbose_name="触发器任务类型", choices=ToolTaskTypeChoices.choices, default=ToolTaskTypeChoices.APPLICATION, max_length=256) source_id = models.UUIDField(verbose_name="资源id") meta = models.JSONField(default=dict, encoder=SystemEncoder) state = models.CharField(verbose_name='状态', max_length=20, choices=State.choices, default=State.STARTED) run_time = models.FloatField(verbose_name="运行时长", default=0) class Meta: db_table = "tool_record" ================================================ FILE: apps/tools/models/tool_workflow.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: tool_workflow.py @date:2026/3/3 13:59 @desc: """ from django.db import models from common.mixins.app_model_mixin import AppModelMixin import uuid_utils.compat as uuid from tools.models import Tool class ToolWorkflow(AppModelMixin): """ 知识库工作流表 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") tool = models.OneToOneField(Tool, on_delete=models.CASCADE, verbose_name="工具", db_constraint=False, related_name='workflow') workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) work_flow = models.JSONField(verbose_name="工作流数据", default=dict) is_publish = models.BooleanField(verbose_name="是否发布", default=False, db_index=True) publish_time = models.DateTimeField(verbose_name="发布时间", null=True, blank=True) class Meta: db_table = "tool_workflow" class ToolWorkflowVersion(AppModelMixin): """ 知识库工作流版本表 - 记录工作流历史版本 """ id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") tool = models.ForeignKey(Tool, on_delete=models.CASCADE, verbose_name="工具", db_constraint=False) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) name = models.CharField(verbose_name="版本名称", max_length=128, default="") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True) publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="") class Meta: db_table = "tool_workflow_version" ================================================ FILE: apps/tools/serializers/__init__.py ================================================ # coding=utf-8 ================================================ FILE: apps/tools/serializers/tool.py ================================================ # -*- coding: utf-8 -*- import asyncio import base64 import io import json import os import pickle import re import tempfile import zipfile from typing import Dict from django.core.cache import cache import requests import uuid_utils.compat as uuid from django.core import validators from django.db import transaction from django.db.models import QuerySet, Q, Subquery, OuterRef, CharField, Value, When, Case from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from langchain_mcp_adapters.client import MultiServerMCPClient from pylint.lint import Run from pylint.reporters import JSON2Reporter from rest_framework import serializers, status from application.models import Application from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import page_search, native_page_search, native_search from common.exception.app_exception import AppApiException from common.field.common import UploadedImageField from common.result import result from common.utils.common import get_file_content from common.utils.logger import maxkb_logger from common.utils.rsa_util import rsa_long_decrypt, rsa_long_encrypt from common.utils.tool_code import ToolExecutor from knowledge.models import File, FileSourceType, Knowledge from maxkb.const import PROJECT_DIR from system_manage.models import AuthTargetType, WorkspaceUserResourcePermission from system_manage.models.resource_mapping import ResourceMapping from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from tools.models import Tool, ToolScope, ToolFolder, ToolType, ToolRecord from tools.models.tool_workflow import ToolWorkflow from trigger.models import TriggerTask, Trigger from users.serializers.user import is_workspace_manage tool_executor = ToolExecutor() class ToolInstance: def __init__(self, tool: dict, version: str): self.tool = tool self.version = version ALLOWED_CLASSES = { ("builtins", "dict"), ('uuid', 'UUID'), ("tools.serializers.tool", "ToolInstance") } def to_dict(message, file_name): return { 'line': message.line, 'column': message.column, 'endLine': message.end_line, 'endColumn': message.end_column, 'message': (message.msg or "").replace(file_name, 'code'), 'type': message.category } def get_file_name(): file_name = f"{uuid.uuid7()}" pylint_dir = os.path.join(PROJECT_DIR, 'data', 'pylint') if not os.path.exists(pylint_dir): os.makedirs(pylint_dir, 0o700, exist_ok=True) os.chmod(os.path.dirname(pylint_dir), 0o700) return os.path.join(pylint_dir, file_name) class RestrictedUnpickler(pickle.Unpickler): def find_class(self, folder, name): if (folder, name) in ALLOWED_CLASSES: return super().find_class(folder, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (folder, name)) def encryption(message: str): """ 加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890 :param message: :return: """ if type(message) != str: return message if message == "": return "" max_pre_len = 8 max_post_len = 4 message_len = len(message) pre_len = int(message_len / 5 * 2) post_len = int(message_len / 5 * 1) pre_str = "".join([message[index] for index in range(0, max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int( pre_len))]) end_str = "".join( [message[index] for index in range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len), message_len)]) content = "***************" return pre_str + content + end_str def validate_mcp_config(servers: Dict): async def validate(): client = MultiServerMCPClient(servers) await client.get_tools() try: asyncio.run(validate()) except Exception as e: maxkb_logger.error(f"validate mcp config error: {e}, servers: {servers}") raise serializers.ValidationError(_('MCP configuration is invalid')) class ToolModelSerializer(serializers.ModelSerializer): class Meta: model = Tool fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params', 'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label', 'version', 'create_time', 'update_time'] class ToolRecordModelSerializer(serializers.ModelSerializer): class Meta: model = ToolRecord fields = ['id', 'workspace_id', 'tool_id', 'source_type', 'source_id', 'meta', 'state', 'run_time', 'create_time', 'update_time'] class ToolExportModelSerializer(serializers.ModelSerializer): class Meta: model = Tool fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label', 'create_time', 'update_time'] class UploadedFileField(serializers.FileField): def __init__(self, **kwargs): super().__init__(**kwargs) def to_representation(self, value): return value class ToolInputField(serializers.Serializer): name = serializers.CharField(required=True, label=_('variable name')) is_required = serializers.BooleanField(required=True, label=_('required')) type = serializers.CharField(required=True, label=_('type'), validators=[ validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"), message=_('fields only support string|int|dict|array|float'), code=500) ]) source = serializers.CharField(required=True, label=_('source'), validators=[ validators.RegexValidator(regex=re.compile("^custom|reference$"), message=_('The field only supports custom|reference'), code=500) ]) class InitField(serializers.Serializer): field = serializers.CharField(required=True, label=_('field name')) label = serializers.CharField(required=True, label=_('field label')) required = serializers.BooleanField(required=True, label=_('required')) input_type = serializers.CharField(required=True, label=_('input type')) default_value = serializers.CharField(required=False, allow_null=True, allow_blank=True) show_default_value = serializers.BooleanField(required=False, default=False) props_info = serializers.DictField(required=False, default=dict) attrs = serializers.DictField(required=False, default=dict) class ToolCreateRequest(serializers.Serializer): name = serializers.CharField(required=True, label=_('tool name')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool description')) code = serializers.CharField(required=True, label=_('tool content')) input_field_list = serializers.ListField(required=False, default=list, label=_('input field list')) init_field_list = serializers.ListField(required=False, default=list, label=_('init field list')) is_active = serializers.BooleanField(required=False, label=_('Is active')) folder_id = serializers.CharField(required=False, allow_null=True) class ToolEditRequest(serializers.Serializer): name = serializers.CharField(required=False, label=_('tool name'), allow_null=True) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool description')) code = serializers.CharField(required=False, label=_('tool content'), allow_null=True, ) input_field_list = serializers.ListField(required=False, default=list, allow_null=True, label=_('input field list')) init_field_list = serializers.ListField(required=False, default=list, allow_null=True, label=_('init field list')) init_params = serializers.DictField(required=False, default=dict, allow_null=True, label=_('init params')) is_active = serializers.BooleanField(required=False, label=_('Is active'), allow_null=True, ) folder_id = serializers.CharField(required=False, allow_null=True) class AddInternalToolRequest(serializers.Serializer): name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True) folder_id = serializers.CharField(required=False, allow_null=True, label=_("folder id")) class DebugField(serializers.Serializer): name = serializers.CharField(required=True, label=_('variable name')) value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('variable value')) class ToolDebugRequest(serializers.Serializer): code = serializers.CharField(required=True, label=_('tool content')) input_field_list = serializers.ListField(required=False, default=list, label=_('input field list')) init_field_list = serializers.ListField(required=False, default=list, label=_('init field list')) init_params = serializers.DictField(required=False, default=dict, label=_('init params')) debug_field_list = DebugField(required=True, many=True) class PylintInstance(serializers.Serializer): code = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('function content')) class ToolSerializer(serializers.Serializer): class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) folder_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('folder id')) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool name')) user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id')) scope = serializers.CharField(required=True, label=_('scope')) tool_type = serializers.CharField(required=False, label=_('tool type'), allow_null=True, allow_blank=True) create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True) def get_query_set(self, workspace_manage, is_x_pack_ee): tool_query_set = QuerySet(Tool).filter(workspace_id=self.data.get('workspace_id')) folder_query_set = QuerySet(ToolFolder) default_query_set = QuerySet(Tool) workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') scope = self.data.get('scope') tool_type = self.data.get('tool_type') desc = self.data.get('desc') name = self.data.get('name') folder_id = self.data.get('folder_id') create_user = self.data.get('create_user') if workspace_id is not None: folder_query_set = folder_query_set.filter(workspace_id=workspace_id) default_query_set = default_query_set.filter(workspace_id=workspace_id) if folder_id is not None and folder_id != workspace_id: folder_query_set = folder_query_set.filter(parent=folder_id) default_query_set = default_query_set.filter(folder_id=folder_id) if name is not None: folder_query_set = folder_query_set.filter(name__icontains=name) default_query_set = default_query_set.filter(name__icontains=name) if desc is not None: folder_query_set = folder_query_set.filter(desc__icontains=desc) default_query_set = default_query_set.filter(desc__icontains=desc) if create_user is not None: tool_query_set = tool_query_set.filter(user_id=create_user) folder_query_set = folder_query_set.filter(user_id=create_user) default_query_set = default_query_set.order_by("-create_time") if scope is not None: tool_query_set = tool_query_set.filter(scope=scope) if tool_type: tool_query_set = tool_query_set.filter(tool_type=tool_type) query_set_dict = { 'tool_query_set': tool_query_set, 'default_query_set': default_query_set, } if not workspace_manage: query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet( WorkspaceUserResourcePermission).filter( auth_target_type="TOOL", workspace_id=workspace_id, user_id=user_id ) return query_set_dict def get_authorized_query_set(self): default_query_set = QuerySet(Tool) tool_type = self.data.get('tool_type') desc = self.data.get('desc') name = self.data.get('name') create_user = self.data.get('create_user') default_query_set = default_query_set.filter(workspace_id='None') default_query_set = default_query_set.filter(scope=ToolScope.SHARED) if name is not None: default_query_set = default_query_set.filter(name__icontains=name) if desc is not None: default_query_set = default_query_set.filter(desc__icontains=desc) if create_user is not None: default_query_set = default_query_set.filter(user_id=create_user) if tool_type: default_query_set = default_query_set.filter(tool_type=tool_type) default_query_set = default_query_set.order_by("-create_time") return default_query_set @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def get_tools(self): self.is_valid(raise_exception=True) workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() results = native_search( self.get_query_set(workspace_manage, is_x_pack_ee), get_file_content( os.path.join( PROJECT_DIR, "apps", "tools", 'sql', 'list_tool.sql' if workspace_manage else ( 'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql' ) ) ), ) get_authorized_tool = DatabaseModelManage.get_model("get_authorized_tool") shared_queryset = QuerySet(Tool).none() if get_authorized_tool is not None: shared_queryset = self.get_authorized_query_set() shared_queryset = get_authorized_tool(shared_queryset, self.data.get('workspace_id')) return { 'shared_tools': [ ToolModelSerializer(data).data for data in shared_queryset ], 'tools': [ { **tool, 'input_field_list': json.loads(tool.get('input_field_list', '[]')), 'init_field_list': json.loads(tool.get('init_field_list', '[]')), } for tool in results if tool['resource_type'] == 'tool' ], } class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) @transaction.atomic def insert(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ToolCreateRequest(data=instance).is_valid(raise_exception=True) # 校验代码是否包括禁止的关键字 if instance.get('tool_type') == ToolType.MCP: ToolExecutor().validate_mcp_transport(instance.get('code', '')) tool_id = uuid.uuid7() Tool( id=tool_id, name=instance.get('name'), desc=instance.get('desc'), code=instance.get('code'), user_id=self.data.get('user_id'), workspace_id=self.data.get('workspace_id'), input_field_list=instance.get('input_field_list', []), init_field_list=instance.get('init_field_list', []), scope=instance.get('scope', ToolScope.WORKSPACE), tool_type=instance.get('tool_type', ToolType.CUSTOM), folder_id=instance.get('folder_id', self.data.get('workspace_id')), is_active=False ).save() # 自动授权给创建者 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource(str(tool_id)) if instance.get('tool_type') == ToolType.WORKFLOW: ToolWorkflow(id=uuid.uuid7(), tool_id=tool_id, work_flow=instance.get('work_flow', {})).save() # 如果是SKILL类型的工具,修改file表中对应的记录 if instance.get('tool_type') == ToolType.SKILL: file_id = instance.get('code') old_file = QuerySet(File).filter(id=file_id).first() if old_file: # 创建新的文件副本,不复制实际文件内容 new_file_id = uuid.uuid7() new_file = File( id=new_file_id, file_name=old_file.file_name, file_size=old_file.file_size, sha256_hash=old_file.sha256_hash, source_type=FileSourceType.TOOL, source_id=tool_id, meta=old_file.meta, ) new_file.save(old_file.get_bytes()) # 更新工具的code为新的文件id QuerySet(Tool).filter(id=tool_id).update(code=str(new_file_id)) return ToolSerializer.Operate(data={ 'id': tool_id, 'workspace_id': self.data.get('workspace_id') }).one() class TestConnection(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) code = serializers.CharField(required=True, label=_('tool content')) def test_connection(self): self.is_valid(raise_exception=True) # 校验代码是否包括禁止的关键字 ToolExecutor().validate_mcp_transport(self.data.get('code', '')) # 校验mcp json validate_mcp_config(json.loads(self.data.get('code'))) return True class Debug(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) def debug(self, debug_instance): self.is_valid(raise_exception=True) ToolDebugRequest(data=debug_instance).is_valid(raise_exception=True) input_field_list = debug_instance.get('input_field_list') code = debug_instance.get('code') debug_field_list = debug_instance.get('debug_field_list') init_params = debug_instance.get('init_params') params = {field.get('name'): self.convert_value(field.get('name'), field.get('value'), field.get('type'), field.get('is_required')) for field in [{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')), **field} for field in input_field_list]} # 合并初始化参数 if init_params is not None: all_params = init_params | params else: all_params = params return tool_executor.exec_code(code, all_params) @staticmethod def get_field_value(debug_field_list, name, is_required): result = [field for field in debug_field_list if field.get('name') == name] if len(result) > 0: return result[-1].get('value') if is_required: raise AppApiException(500, f"{name}" + _('field has no value set')) return None @staticmethod def convert_value(name: str, value: str, _type: str, is_required: bool): if not is_required and (value is None or (isinstance(value, str) and len(value.strip()) == 0)): return None try: if _type == 'int': return int(value) if _type == 'boolean': value = 0 if ['0', '[]'].__contains__(value) else value return bool(value) if _type == 'float': return float(value) if _type == 'dict': v = json.loads(value) if isinstance(v, dict): return v raise Exception(_('type error')) if _type == 'array': v = json.loads(value) if isinstance(v, list): return v raise Exception(_('type error')) return value except Exception as e: raise AppApiException(500, _('Field: {name} Type: {_type} Value: {value} Type conversion error').format( name=name, type=_type, value=value )) class Operate(serializers.Serializer): id = serializers.UUIDField(required=True, label=_('tool id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) def is_one_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Tool).filter(id=self.data.get('id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): get_authorized_tool = DatabaseModelManage.get_model('get_authorized_tool') if get_authorized_tool: if not get_authorized_tool(QuerySet(Tool).filter(id=self.data.get('id')), workspace_id).exists(): raise AppApiException(500, _('Tool id does not exist')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Tool).filter(id=self.data.get('id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Tool id does not exist')) @transaction.atomic def edit(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ToolEditRequest(data=instance).is_valid(raise_exception=True) # 校验代码是否包括禁止的关键字 if instance.get('tool_type') == ToolType.MCP: ToolExecutor().validate_mcp_transport(instance.get('code', '')) if not QuerySet(Tool).filter(id=self.data.get('id')).exists(): raise serializers.ValidationError(_('Tool not found')) edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params', 'is_active', 'folder_id'] edit_dict = {field: instance.get(field) for field in edit_field_list if ( field in instance and instance.get(field) is not None)} tool = QuerySet(Tool).filter(id=self.data.get('id')).first() if 'init_params' in edit_dict: if edit_dict['init_field_list'] is not None: rm_key = [] for key in edit_dict['init_params']: if key not in [field['field'] for field in edit_dict['init_field_list']]: rm_key.append(key) for key in rm_key: edit_dict['init_params'].pop(key) if tool.init_params: old_init_params = json.loads(rsa_long_decrypt(tool.init_params)) for key in edit_dict['init_params']: if key in old_init_params and edit_dict['init_params'][key] == encryption(old_init_params[key]): edit_dict['init_params'][key] = old_init_params[key] edit_dict['init_params'] = rsa_long_encrypt(json.dumps(edit_dict['init_params'])) edit_dict['update_time'] = timezone.now() QuerySet(Tool).filter(id=self.data.get('id')).update(**edit_dict) if 'is_active' in instance: QuerySet(TriggerTask).filter(source_type="TOOL", source_id=self.data.get('id')).update( is_active=instance.get('is_active')) # 如果是SKILL类型的工具,修改file表中对应的记录 if instance.get('tool_type') == ToolType.SKILL: old_file_id = tool.code file_id = instance.get('code') if old_file_id != file_id: QuerySet(File).filter(id=old_file_id).delete() QuerySet(File).filter(id=file_id).update(source_id=tool.id, source_type=FileSourceType.TOOL) return self.one() @transaction.atomic def delete(self): from trigger.handler.simple_tools import deploy from trigger.serializers.trigger import TriggerModelSerializer self.is_valid(raise_exception=True) tool = QuerySet(Tool).filter(id=self.data.get('id')).first() if tool.template_id is None and tool.icon != '': QuerySet(File).filter(id=tool.icon.split('/')[-1]).delete() if tool.tool_type == ToolType.SKILL: QuerySet(File).filter(id=tool.code).delete() QuerySet(WorkspaceUserResourcePermission).filter(target=tool.id).delete() QuerySet(Tool).filter(id=self.data.get('id')).delete() ResourceMapping.objects.filter(target_id=self.data.get('id')).delete() QuerySet(ToolRecord).filter(tool_id=self.data.get('id')).delete() trigger_ids = list( QuerySet(TriggerTask).filter( source_type="TOOL", source_id=self.data.get('id') ).values('trigger_id').distinct() ) QuerySet(TriggerTask).filter(source_type="TOOL", source_id=self.data.get('id')).delete() for trigger_id in trigger_ids: trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first() if trigger and trigger.is_active: deploy(TriggerModelSerializer(trigger).data, **{}) def one(self): self.is_one_valid(raise_exception=True) tool = QuerySet(Tool).filter(id=self.data.get('id')).select_related('user').first() nick_name = tool.user.nick_name if tool and tool.user else None if tool.init_params: tool.init_params = json.loads(rsa_long_decrypt(tool.init_params)) if tool.init_field_list: password_fields = [i["field"] for i in tool.init_field_list if i.get("input_type") == "PasswordInput"] if tool.init_params: for k in tool.init_params: if k in password_fields and tool.init_params[k]: tool.init_params[k] = encryption(tool.init_params[k]) if tool.tool_type == 'SKILL': skill_file = QuerySet(File).filter(id=tool.code).first() skill_file_dict = { 'id': str(skill_file.id), 'name': skill_file.file_name, 'size': skill_file.file_size, } if skill_file else None work_flow = {} if tool.tool_type == 'WORKFLOW': tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool.id).first() if tool_workflow: work_flow = tool_workflow.work_flow return { **ToolModelSerializer(tool).data, 'init_params': tool.init_params if tool.init_params else {}, 'nick_name': nick_name, 'fileList': [skill_file_dict] if tool.tool_type == 'SKILL' else [], 'work_flow': work_flow } def export(self): try: self.is_valid() id = self.data.get('id') tool = QuerySet(Tool).filter(id=id).first() tool_dict = ToolExportModelSerializer(tool).data # 如果是SKILL类型的工具,校验文件是否存在 if tool.tool_type == ToolType.SKILL: skill_file = QuerySet(File).filter(id=tool.code).first() if skill_file: tool_dict['code'] = base64.b64encode(skill_file.get_bytes()).decode('utf-8') mk_instance = ToolInstance(tool_dict, 'v2') tool_pickle = pickle.dumps(mk_instance) response = HttpResponse(content_type='text/plain', content=tool_pickle) response['Content-Disposition'] = f'attachment; filename="{tool.name}.tool"' return response except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) class Pylint(serializers.Serializer): def run(self, instance, is_valid=True): if is_valid: self.is_valid(raise_exception=True) PylintInstance(data=instance).is_valid(raise_exception=True) code = instance.get('code') file_name = get_file_name() with open(file_name, 'w') as file: file.write(code) reporter = JSON2Reporter(output=io.StringIO()) Run([file_name, "--disable=line-too-long", '--module-rgx=[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'], reporter=reporter, exit=False) os.remove(file_name) return [to_dict(m, os.path.basename(file_name)) for m in reporter.messages] class Import(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) folder_id = serializers.CharField(required=False, allow_null=True, label=_("folder id")) # @transaction.atomic def import_(self, scope=ToolScope.WORKSPACE): self.is_valid() user_id = self.data.get('user_id') tool_instance_bytes = self.data.get('file').read() try: tool_instance = RestrictedUnpickler(io.BytesIO(tool_instance_bytes)).load() except Exception as e: raise AppApiException(1001, _("Unsupported file format")) if self.data.get('folder_id') is None: folder_id = self.data.get('workspace_id') else: folder_id = self.data.get('folder_id') tool = tool_instance.tool tool_id = uuid.uuid7() code = tool.get('code') if tool.get('tool_type') == ToolType.SKILL: skill_file_id = uuid.uuid7() skill_file = File( id=skill_file_id, file_name=f"{tool.get('name')}.zip", source_type=FileSourceType.TOOL, source_id=tool_id, meta={} ) skill_file.save(base64.b64decode(code)) code = skill_file_id tool_model = Tool( id=tool_id, name=tool.get('name'), desc=tool.get('desc'), code=code, user_id=user_id, workspace_id=self.data.get('workspace_id'), input_field_list=tool.get('input_field_list'), init_field_list=tool.get('init_field_list', []), tool_type=tool.get('tool_type'), folder_id=folder_id, scope=scope, is_active=False ) tool_model.save() # 自动授权给创建者 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource(str(tool_id)) return True class IconOperate(serializers.Serializer): id = serializers.UUIDField(required=True, label=_("function ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) user_id = serializers.UUIDField(required=True, label=_("User ID")) image = UploadedImageField(required=True, label=_("picture")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Tool).filter(id=self.data.get('id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Tool id does not exist')) def edit(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) tool = QuerySet(Tool).filter(id=self.data.get('id')).first() if tool is None: raise AppApiException(500, _('Function does not exist')) # 删除旧的图片 if tool.icon != '': QuerySet(File).filter(id=tool.icon.split('/')[-1]).delete() if self.data.get('image') is None: tool.icon = '' else: meta = { 'debug': False } file_id = uuid.uuid7() file = File( id=file_id, file_name=self.data.get('image').name, source_type=FileSourceType.TOOL, source_id=tool.id, meta=meta ) file.save(self.data.get('image').read()) tool.icon = f'./oss/file/{file_id}' tool.save() return tool.icon class InternalTool(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True) def get_internal_tools(self): self.is_valid(raise_exception=True) query_set = QuerySet(Tool) if self.data.get('name', '') != '': query_set = query_set.filter( Q(name__icontains=self.data.get('name')) | Q(desc__icontains=self.data.get('name')) ) query_set = query_set.filter( Q(scope=ToolScope.INTERNAL) & Q(is_active=True) ) return ToolModelSerializer(query_set, many=True).data class AddInternalTool(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) tool_id = serializers.UUIDField(required=True, label=_("tool id")) def add(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) AddInternalToolRequest(data=instance).is_valid(raise_exception=True) internal_tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first() if internal_tool is None: raise AppApiException(500, _('Tool does not exist')) tool_id = uuid.uuid7() tool = Tool( id=tool_id, name=instance.get('name', internal_tool.name), desc=internal_tool.desc, code=internal_tool.code, user_id=self.data.get('user_id'), icon=internal_tool.icon, workspace_id=self.data.get('workspace_id'), input_field_list=internal_tool.input_field_list, init_field_list=internal_tool.init_field_list, scope=ToolScope.WORKSPACE, tool_type=ToolType.CUSTOM, folder_id=instance.get('folder_id', self.data.get('workspace_id')), template_id=internal_tool.id, label=internal_tool.label, is_active=False ) tool.save() # 自动授权给创建者 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource(str(tool_id)) return ToolModelSerializer(tool).data class StoreTool(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True) def get_appstore_tools(self): self.is_valid(raise_exception=True) # 下载zip文件 try: res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5) res.raise_for_status() # 创建临时文件保存zip with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip: temp_zip.write(res.content) temp_zip_path = temp_zip.name try: # 解压zip文件 with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref: # 获取zip中的第一个文件(假设只有一个json文件) json_filename = zip_ref.namelist()[0] json_content = zip_ref.read(json_filename) # 将json转换为字典 tool_store = json.loads(json_content.decode('utf-8')) tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']} filter_apps = [] for tool in tool_store['apps']: if self.data.get('name', '') != '': if self.data.get('name').lower() not in tool.get('name', '').lower(): continue if not tool['downloadUrl'].endswith('.tool'): continue versions = tool.get('versions', []) tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else '' tool['version'] = next( (version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']), ) filter_apps.append(tool) tool_store['apps'] = filter_apps return tool_store finally: # 清理临时文件 os.unlink(temp_zip_path) except Exception as e: maxkb_logger.error(f"fetch appstore tools error: {e}") return {'apps': [], 'additionalProperties': {'tags': []}} class AddStoreTool(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) tool_id = serializers.CharField(required=True, label=_("tool id")) def add(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) AddInternalToolRequest(data=instance).is_valid(raise_exception=True) versions = instance.get('versions', []) download_url = instance.get('download_url') # 查找匹配的版本名称 version_name = next( (version.get('name') for version in versions if version.get('downloadUrl') == download_url), ) res = requests.get(download_url, timeout=5) tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool tool_id = uuid.uuid7() # 如果是SKILL类型的工具,保存文件内容到file表,并将code替换为file_id if tool_data.get('tool_type') == ToolType.SKILL: skill_file_id = uuid.uuid7() skill_file = File( id=skill_file_id, file_name=f"{tool_data.get('name')}.zip", source_type=FileSourceType.TOOL, source_id=tool_id, meta={} ) skill_file.save(base64.b64decode(tool_data.get('code'))) tool_data['code'] = skill_file_id tool = Tool( id=tool_id, name=instance.get('name'), desc=tool_data.get('desc'), code=tool_data.get('code'), user_id=self.data.get('user_id'), icon=instance.get('icon', ''), workspace_id=self.data.get('workspace_id'), input_field_list=tool_data.get('input_field_list', []), init_field_list=tool_data.get('init_field_list', []), scope=ToolScope.WORKSPACE, tool_type=tool_data.get('tool_type', ToolType.CUSTOM), folder_id=instance.get('folder_id', self.data.get('workspace_id')), template_id=self.data.get('tool_id'), label=instance.get('label'), version=version_name, is_active=False ) tool.save() # 自动授权给创建者 UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource(str(tool_id)) try: requests.get(instance.get('download_callback_url'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return ToolModelSerializer(tool).data class UpdateStoreTool(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) tool_id = serializers.UUIDField(required=True, label=_("tool id")) download_url = serializers.CharField(required=True, label=_("download url")) download_callback_url = serializers.CharField(required=True, label=_("download callback url")) icon = serializers.CharField(required=True, label=_("icon"), allow_null=True, allow_blank=True) versions = serializers.ListField(required=True, label=_("versions"), child=serializers.DictField()) def update_tool(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first() if tool is None: raise AppApiException(500, _('Tool does not exist')) # 查找匹配的版本名称 version_name = next( (version.get('name') for version in self.data.get('versions') if version.get('downloadUrl') == self.data.get('download_url')), ) res = requests.get(self.data.get('download_url'), timeout=5) tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool # 如果是SKILL类型的工具,保存文件内容到file表,并将code替换为file_id if tool_data.get('tool_type') == ToolType.SKILL: skill_file_id = uuid.uuid7() skill_file = File( id=skill_file_id, file_name=f"{tool_data.get('name')}.zip", source_type=FileSourceType.TOOL, source_id=tool.id, meta={} ) skill_file.save(base64.b64decode(tool_data.get('code'))) tool_data['code'] = skill_file_id tool.desc = tool_data.get('desc') tool.code = tool_data.get('code') tool.input_field_list = tool_data.get('input_field_list', []) tool.init_field_list = tool_data.get('init_field_list', []) tool.icon = self.data.get('icon', tool.icon) tool.version = version_name # tool.is_active = False tool.save() try: requests.get(self.data.get('download_callback_url'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return ToolModelSerializer(tool).data class ToolRecord(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, label=_('workspace id')) tool_id = serializers.UUIDField(required=True, label=_('tool id')) record_id = serializers.UUIDField(required=False, allow_null=True, label=_('record id')) source_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source name')) source_type = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source type')) state = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('state')) class Operate(serializers.Serializer): id = serializers.UUIDField(required=False, allow_null=True, label=_('record id')) tool_id = serializers.UUIDField(required=True, label=_('tool id')) workspace_id = serializers.CharField(required=False, allow_null=True, label=_('workspace id')) def one(self): self.is_valid(raise_exception=True) tool_record = cache.get(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.data.get('id')), version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version()) if tool_record: return tool_record tool_record = QuerySet(ToolRecord).filter(id=self.data.get('id'), tool_id=self.data.get('tool_id'), workspace_id=self.data.get('workspace_id')).first() if tool_record: return {'id': tool_record.id, 'tool_id': tool_record.tool_id, 'workspace_id': tool_record.workspace_id, 'source_type': tool_record.source_type, 'source_id': tool_record.source_id, 'meta': tool_record.meta, 'state': tool_record.state, 'run_time': tool_record.run_time} raise AppApiException(500, _('Tool record does not exist')) def one(self): self.is_valid(raise_exception=True) if self.data.get('record_id'): page = self.get_tool_records(1, 1) return page.get('records')[0] return None def get_tool_records(self, current_page: int, page_size: int): self.is_valid(raise_exception=True) application_subquery = Application.objects.filter(id=OuterRef('source_id')).values('name')[:1] knowledge_subquery = Knowledge.objects.filter(id=OuterRef('source_id')).values('name')[:1] trigger_subquery = Trigger.objects.filter(id=OuterRef('source_id')).values('name')[:1] trigger_type_subquery = Trigger.objects.filter(id=OuterRef('source_id')).values('trigger_type')[:1] query_set = QuerySet(ToolRecord) query_set = query_set.filter( tool_id=self.data.get('tool_id') ).annotate( source_name=Case( When(source_type='APPLICATION', then=Subquery(application_subquery)), When(source_type='KNOWLEDGE', then=Subquery(knowledge_subquery)), When(source_type='TRIGGER', then=Subquery(trigger_subquery)), default=Value(''), output_field=CharField() ) ).annotate( trigger_type=Case( When(source_type='TRIGGER', then=Subquery(trigger_type_subquery)), default=Value(''), output_field=CharField() ) ).annotate( tool_name=Subquery( Tool.objects.filter(id=OuterRef('tool_id')).values('name')[:1] ) ).annotate( tool_icon=Subquery( Tool.objects.filter(id=OuterRef('tool_id')).values('icon')[:1] ) ) if self.data.get('source_type'): query_set = query_set.filter(Q(source_type=self.data.get('source_type', ''))) if self.data.get('state'): query_set = query_set.filter(Q(state=self.data.get('state', ''))) if self.data.get('source_name'): query_set = query_set.filter(Q(source_name__icontains=self.data.get('source_name', ''))) if self.data.get('record_id'): query_set = query_set.filter(Q(id=self.data.get('record_id'))) if self.data.get('workspace_id'): query_set = query_set.filter(Q(workspace_id=self.data.get('workspace_id'))) query_set = query_set.order_by('-create_time') return page_search( current_page, page_size, query_set, lambda record: { **ToolRecordModelSerializer(record).data, 'source_name': record.source_name, 'tool_name': record.tool_name, 'tool_icon': record.tool_icon, 'trigger_type': record.trigger_type, } ) class UploadSkillFile(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_("workspace id")) def upload(self): self.is_valid() file = self.data.get('file') if not file.name.endswith('.zip'): raise AppApiException(1001, _("Unsupported file format")) file_id = uuid.uuid7() file = File( id=file_id, file_name=self.data.get('file').name, meta={} ) file.save(self.data.get('file').read()) return file_id class ToolTreeSerializer(serializers.Serializer): class Query(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) folder_id = serializers.CharField(required=True, label=_('folder id')) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool name')) user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id')) scope = serializers.CharField(required=True, label=_('scope')) tool_type = serializers.CharField(required=False, label=_('tool type'), allow_null=True, allow_blank=True) tool_type_list = serializers.ListField(child=serializers.CharField(), required=False, label=_('tool type list'), allow_null=True, allow_empty=True) create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True) def page_tool(self, current_page: int, page_size: int): self.is_valid(raise_exception=True) folder_id = self.data.get('folder_id', self.data.get('workspace_id')) root = ToolFolder.objects.filter(id=folder_id).first() if not root: raise serializers.ValidationError(_('Folder not found')) # 使用MPTT的get_descendants()方法获取所有相关节点 all_folders = root.get_descendants(include_self=True) if self.data.get('name'): tools = QuerySet(Tool).filter( Q(workspace_id=self.data.get('workspace_id')) & Q(folder_id__in=all_folders) & Q(user_id=self.data.get('user_id')) & Q(name__contains=self.data.get('name')) ) else: tools = QuerySet(Tool).filter( Q(workspace_id=self.data.get('workspace_id')) & Q(folder_id__in=all_folders) & Q(user_id=self.data.get('user_id')) ) return page_search(current_page, page_size, tools, lambda record: ToolModelSerializer(record).data) def get_query_set(self, workspace_manage, is_x_pack_ee): tool_query_set = QuerySet(Tool).filter(workspace_id=self.data.get('workspace_id')) folder_query_set = QuerySet(ToolFolder) default_query_set = QuerySet(Tool) workspace_id = self.data.get('workspace_id') user_id = self.data.get('user_id') scope = self.data.get('scope') tool_type = self.data.get('tool_type') desc = self.data.get('desc') name = self.data.get('name') folder_id = self.data.get('folder_id') create_user = self.data.get('create_user') if workspace_id is not None: folder_query_set = folder_query_set.filter(workspace_id=workspace_id) default_query_set = default_query_set.filter(workspace_id=workspace_id) if folder_id is not None and folder_id != workspace_id: folder_query_set = folder_query_set.filter(parent=folder_id) default_query_set = default_query_set.filter(folder_id=folder_id) if name is not None: folder_query_set = folder_query_set.filter(name__icontains=name) default_query_set = default_query_set.filter(name__icontains=name) if desc is not None: folder_query_set = folder_query_set.filter(desc__icontains=desc) default_query_set = default_query_set.filter(desc__icontains=desc) if create_user is not None: tool_query_set = tool_query_set.filter(user_id=create_user) folder_query_set = folder_query_set.filter(user_id=create_user) default_query_set = default_query_set.order_by("-create_time") if scope is not None: tool_query_set = tool_query_set.filter(scope=scope) tool_type_list = self.data.get('tool_type_list') if tool_type_list: tool_query_set = tool_query_set.filter(tool_type__in=tool_type_list) elif tool_type: tool_query_set = tool_query_set.filter(tool_type=tool_type) query_set_dict = { 'tool_query_set': tool_query_set, 'default_query_set': default_query_set, } if not workspace_manage: query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet( WorkspaceUserResourcePermission).filter( auth_target_type="TOOL", workspace_id=workspace_id, user_id=user_id ) return query_set_dict @staticmethod def is_x_pack_ee(): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None def page_tool_with_folders(self, current_page: int, page_size: int): self.is_valid(raise_exception=True) workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() result = native_page_search( current_page, page_size, self.get_query_set(workspace_manage, is_x_pack_ee), get_file_content( os.path.join( PROJECT_DIR, "apps", "tools", 'sql', 'list_tool.sql' if workspace_manage else ( 'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql' ) ) ), post_records_handler=lambda record: { **record, 'input_field_list': json.loads(record.get('input_field_list', '[]')), 'init_field_list': json.loads(record.get('init_field_list', '[]')), }, ) return ResourceMappingSerializer().get_resource_count(result) def get_tools(self): self.is_valid(raise_exception=True) workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id')) is_x_pack_ee = self.is_x_pack_ee() results = native_search( self.get_query_set(workspace_manage, is_x_pack_ee), get_file_content( os.path.join( PROJECT_DIR, "apps", "tools", 'sql', 'list_tool.sql' if workspace_manage else ( 'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql' ) ) ), ) # 返回包含文件夹和工具的结构 return { 'folders': [ folder for folder in results if folder['resource_type'] == 'folder' ], 'tools': [ { **tool, 'input_field_list': json.loads(tool.get('input_field_list', '[]')), 'init_field_list': json.loads(tool.get('init_field_list', '[]')), } for tool in results if tool['resource_type'] == 'tool' ], } ================================================ FILE: apps/tools/serializers/tool_folder.py ================================================ # -*- coding: utf-8 -*- from rest_framework import serializers from tools.models import ToolFolder class ToolFolderTreeSerializer(serializers.ModelSerializer): children = serializers.SerializerMethodField() class Meta: model = ToolFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children','create_time','update_time'] def get_children(self, obj): return ToolFolderTreeSerializer(obj.get_children(), many=True).data class ToolFolderFlatSerializer(serializers.ModelSerializer): """只序列化当前层的文件夹,不包含子节点""" class Meta: model = ToolFolder fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id'] ================================================ FILE: apps/tools/serializers/tool_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: KnowledgeVersionSerializer.py @date:2025/11/28 18:00 @desc: """ from typing import Dict from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.db.search import page_search from common.exception.app_exception import AppApiException from tools.models import ToolWorkflowVersion, Tool class ToolWorkflowVersionEditSerializer(serializers.Serializer): name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True, label=_("Version Name")) class ToolVersionModelSerializer(serializers.ModelSerializer): class Meta: model = ToolWorkflowVersion fields = ['id', 'name', 'workspace_id', 'tool_id', 'work_flow', 'publish_user_id', 'publish_user_name', 'create_time', 'update_time'] class ToolWorkflowVersionQuerySerializer(serializers.Serializer): tool_id = serializers.UUIDField(required=True, label=_("Tool ID")) name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("summary")) class ToolWorkflowVersionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, label=_("Workspace ID")) class Query(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def get_query_set(self, query): query_set = QuerySet(ToolWorkflowVersion).filter(tool_id=query.get('tool_id')) if 'name' in query and query.get('name') is not None: query_set = query_set.filter(name__contains=query.get('name')) if 'workspace_id' in self.data and self.data.get('workspace_id') is not None: query_set = query_set.filter(workspace_id=self.data.get('workspace_id')) return query_set.order_by("-create_time") def list(self, query, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ToolWorkflowVersionQuerySerializer(data=query).is_valid(raise_exception=True) query_set = self.get_query_set(query) return [ToolVersionModelSerializer(v).data for v in query_set] def page(self, query, current_page, page_size, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return page_search(current_page, page_size, self.get_query_set(query), post_records_handler=lambda v: ToolVersionModelSerializer(v).data) class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) tool_id = serializers.UUIDField(required=True, label=_("Tool ID")) tool_version_id = serializers.UUIDField(required=True, label=_("Tool version ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Tool).filter(id=self.data.get('tool_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Tool id does not exist')) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'), id=self.data.get( 'tool_version_id')).first() if tool_version is not None: return ToolVersionModelSerializer(tool_version).data else: raise AppApiException(500, _('Workflow version does not exist')) def edit(self, instance: Dict, with_valid=True): if with_valid: self.is_valid(raise_exception=True) ToolWorkflowVersionEditSerializer(data=instance).is_valid(raise_exception=True) tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'), id=self.data.get( 'knowledge_version_id')).first() if tool_version is not None: name = instance.get('name', None) if name is not None and len(name) > 0: tool_version.name = name tool_version.save() return ToolVersionModelSerializer(tool_version).data else: raise AppApiException(500, _('Workflow version does not exist')) ================================================ FILE: apps/tools/serializers/tool_workflow.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: tool_workflow.py @date:2026/3/6 13:59 @desc: """ # coding=utf-8 import pickle from functools import reduce from typing import Dict, List import requests import uuid_utils.compat as uuid from django.db import transaction from django.db.models import QuerySet from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework import serializers, status from application.flow.common import Workflow, WorkflowMode from application.flow.i_step_node import ToolWorkflowPostHandler from application.flow.tool_workflow_manage import ToolWorkflowManage from application.models import ChatRecord from application.serializers.common import ToolExecute from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField from common.result import result from common.utils.common import bytes_to_uploaded_file from common.utils.common import restricted_loads, generate_uuid from common.utils.logger import maxkb_logger from common.utils.tool_code import ToolExecutor from knowledge.models import KnowledgeWorkflow from system_manage.models import AuthTargetType from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from tools.models import Tool, ToolScope, ToolWorkflow, ToolWorkflowVersion from tools.serializers.tool import ToolExportModelSerializer from users.models import User tool_executor = ToolExecutor() def hand_node(node, update_tool_map): if node.get('type') == 'tool-lib-node': tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '') node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id) if node.get('type') == 'search-knowledge-node': node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = [] if node.get('type') == 'ai-chat-node': node_data = node.get('properties', {}).get('node_data', {}) mcp_tool_ids = node_data.get('mcp_tool_ids') or [] node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in mcp_tool_ids] tool_ids = node_data.get('tool_ids') or [] node_data['tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in tool_ids] if node.get('type') == 'mcp-node': mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '') node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id, mcp_tool_id) class ToolWorkflowModelSerializer(serializers.ModelSerializer): class Meta: model = ToolWorkflow fields = '__all__' class ToolWorkflowImportRequest(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) class ToolWorkflowActionListQuerySerializer(serializers.Serializer): user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True) state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True) class ToolWorkflowInstance: def __init__(self, knowledge_workflow: dict, version: str, tool_list: List[dict]): self.knowledge_workflow = knowledge_workflow self.version = version self.tool_list = tool_list def get_tool_list(self): return self.tool_list or [] class ToolWorkflowSerializer(serializers.Serializer): class Import(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) @transaction.atomic def import_(self, instance: dict, is_import_tool, with_valid=True): if with_valid: self.is_valid() ToolWorkflowSerializer(data=instance).is_valid(raise_exception=True) user_id = self.data.get('user_id') workspace_id = self.data.get('workspace_id') tool_id = self.data.get('tool_id') tool_instance_bytes = instance.get('file').read() try: tool_instance = restricted_loads(tool_instance_bytes) except Exception as e: raise AppApiException(1001, _("Unsupported file format")) tool_workflow = tool_instance.work_flow tool_list = tool_instance.get_tool_list() update_tool_map = {} if len(tool_list) > 0: tool_id_list = reduce(lambda x, y: [*x, *y], [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))] for tool in tool_list], []) # 存在的工具列表 exits_tool_id_list = [str(tool.id) for tool in QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)] # 需要更新的工具集合 update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id'))} tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if not exits_tool_id_list.__contains__( tool.get('id')) and not exits_tool_id_list.__contains__( generate_uuid((tool.get('id') + workspace_id or '')))] work_flow = self.to_tool_workflow( tool_workflow, update_tool_map, ) tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list] QuerySet(ToolWorkflow).filter(workspace_id=workspace_id, tool_id=tool_id).update_or_create( tool_id=tool_id, workspace_id=workspace_id, defaults={'work_flow': work_flow} ) if is_import_tool: if len(tool_model_list) > 0: QuerySet(Tool).bulk_create(tool_model_list) UserResourcePermissionSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id'), 'auth_target_type': AuthTargetType.TOOL.value }).auth_resource_batch([t.id for t in tool_model_list]) @staticmethod def to_tool_workflow(knowledge_workflow, update_tool_map): work_flow = knowledge_workflow.get("work_flow") for node in work_flow.get('nodes', []): hand_node(node, update_tool_map) if node.get('type') == 'loop_node': for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): hand_node(n, update_tool_map) return work_flow @staticmethod def to_tool(tool, workspace_id, user_id): return Tool(id=tool.get('id'), user_id=user_id, name=tool.get('name'), code=tool.get('code'), template_id=tool.get('template_id'), input_field_list=tool.get('input_field_list'), init_field_list=tool.get('init_field_list'), is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM', scope=ToolScope.SHARED if workspace_id == 'None' else ToolScope.WORKSPACE, folder_id='default' if workspace_id == 'None' else workspace_id, workspace_id=workspace_id) class Export(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) tool_id = serializers.UUIDField(required=True, label=_('knowledge id')) def export(self, with_valid=True): try: if with_valid: self.is_valid() tool_id = self.data.get('tool_id') tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool_id).first() tool = QuerySet(Tool).filter(id=tool_id).first() from application.flow.tools import get_tool_id_list tool_id_list = get_tool_id_list(tool_workflow.work_flow) tool_list = [] if len(tool_id_list) > 0: tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) tool_workflow_dict = {'id': tool.id, 'work_flow': tool_workflow.work_flow, 'workspace_id': tool.workspace_id, 'name': tool.name, 'desc': tool.desc, 'tool_type': tool.tool_type} tool_workflow_instance = ToolWorkflowInstance( tool_workflow_dict, 'v2', [ToolExportModelSerializer(tool).data for tool in tool_list] ) tool_workflow_pickle = pickle.dumps(tool_workflow_instance) response = HttpResponse(content_type='text/plain', content=tool_workflow_pickle) response['Content-Disposition'] = f'attachment; filename="{tool.name}.tool"' return response except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) class Operate(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) tool_id = serializers.UUIDField(required=True, label=_('tool id')) def debug(self, instance: Dict, user, with_valid=True): if with_valid: self.is_valid(raise_exception=True) tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get("tool_id")).first() tool_record_id = instance.get('chat_record_id') or str(uuid.uuid7()) took_execute = ToolExecute(self.data.get("tool_id"), tool_record_id, self.data.get("workspace_id"), None, None, True) record = took_execute.get_record() work_flow_manage = ToolWorkflowManage( Workflow.new_instance(tool_workflow.work_flow, WorkflowMode.TOOL), { 'chat_record_id': tool_record_id, 'tool_id': self.data.get("tool_id"), 'stream': True, 'workspace_id': self.data.get("workspace_id"), **instance}, ToolWorkflowPostHandler(took_execute, self.data.get("tool_id")), is_the_task_interrupted=lambda: False, child_node=instance.get('child_node'), start_node_id=instance.get('runtime_node_id'), start_node_data=instance.get('node_data'), chat_record=self.to_chat_record(record) ) r = work_flow_manage.run() return r @staticmethod def to_chat_record(record): if record is None: return None return ChatRecord( answer_text_list=record.meta.get('answer_text_list'), details=record.meta.get('details'), answer_text='', ) def publish(self, with_valid=True): if with_valid: self.is_valid() user_id = self.data.get('user_id') workspace_id = self.data.get("workspace_id") user = QuerySet(User).filter(id=user_id).first() tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get("tool_id"), workspace_id=workspace_id).first() work_flow_version = ToolWorkflowVersion(work_flow=tool_workflow.work_flow, tool_id=self.data.get("tool_id"), name=timezone.localtime(timezone.now()).strftime( '%Y-%m-%d %H:%M:%S'), publish_user_id=user_id, publish_user_name=user.username, workspace_id=workspace_id) work_flow_version.save() QuerySet(ToolWorkflow).filter( tool_id=self.data.get("tool_id") ).update(is_publish=True, publish_time=timezone.now()) return True def edit(self, instance: Dict): self.is_valid(raise_exception=True) if instance.get("work_flow"): QuerySet(ToolWorkflow).update_or_create(tool_id=self.data.get("tool_id"), create_defaults={'id': uuid.uuid7(), 'tool_id': self.data.get( "tool_id"), "workspace_id": self.data.get( 'workspace_id'), 'work_flow': instance.get('work_flow', {}), }, defaults={ 'work_flow': instance.get('work_flow') }) return self.one() if instance.get("work_flow_template"): template_instance = instance.get('work_flow_template') download_url = template_instance.get('downloadUrl') # 查找匹配的版本名称 res = requests.get(download_url, timeout=5) ToolWorkflowSerializer.Import(data={ 'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id'), 'tool_id': str(self.data.get('tool_id')), }).import_({'file': bytes_to_uploaded_file(res.content, 'file.tool')}, is_import_tool=False) try: requests.get(template_instance.get('downloadCallbackUrl'), timeout=5) except Exception as e: maxkb_logger.error(f"callback appstore tool download error: {e}") return self.one() def one(self): self.is_valid(raise_exception=True) workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first() return {**ToolWorkflowModelSerializer(workflow).data} ================================================ FILE: apps/tools/sql/list_tool.sql ================================================ select * from (select tool."id"::text, tool."name", tool."desc", tool."tool_type", tool."scope", 'tool' as "resource_type", tool."workspace_id", tool."folder_id", tool."user_id", "user".nick_name as "nick_name", tool."icon", tool.label, tool."template_id"::text, tool."create_time", tool."update_time", tool.init_field_list, tool.input_field_list, tool.version, tool."is_active" from tool left join "user" on "user".id = user_id ${tool_query_set} ) temp ${default_query_set} ================================================ FILE: apps/tools/sql/list_tool_user.sql ================================================ SELECT * FROM (SELECT tool."id"::text, tool."name", tool."desc", tool."tool_type", tool."scope", 'tool' AS "resource_type", tool."workspace_id", tool."folder_id", tool."user_id", "user".nick_name AS "nick_name", tool."icon", tool.label, tool."template_id"::text, tool."create_time", tool."update_time", tool.init_field_list, tool.input_field_list, tool.version, tool."is_active" FROM (SELECT tool.* FROM tool tool ${tool_query_set} AND tool.id::text IN (SELECT target FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} AND 'VIEW' = ANY (permission_list))) AS tool LEFT JOIN "user" ON "user".id = user_id ) temp ${default_query_set} ================================================ FILE: apps/tools/sql/list_tool_user_ee.sql ================================================ SELECT * FROM (SELECT tool."id"::text, tool."name", tool."desc", tool."tool_type", tool."scope", 'tool' AS "resource_type", tool."workspace_id", tool."folder_id", tool."user_id", "user".nick_name AS "nick_name", tool."icon", tool.label, tool."template_id"::text, tool."create_time", tool."update_time", tool.init_field_list, tool.input_field_list, tool.version, tool."is_active" FROM (SELECT tool.* FROM tool tool ${tool_query_set} AND tool.id::text IN (SELECT target FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set} AND CASE WHEN auth_type = 'ROLE' THEN 'ROLE' = ANY (permission_list) AND 'TOOL:READ' IN (SELECT (CASE WHEN user_role_relation.role_id = ANY (ARRAY ['USER']) THEN 'TOOL:READ' ELSE role_permission.permission_id END) FROM role_permission role_permission RIGHT JOIN user_role_relation user_role_relation ON user_role_relation.role_id=role_permission.role_id WHERE user_role_relation.user_id=workspace_user_resource_permission.user_id AND user_role_relation.workspace_id=workspace_user_resource_permission.workspace_id) ELSE 'VIEW' = ANY (permission_list) END )) AS tool LEFT JOIN "user" ON "user".id = user_id ) temp ${default_query_set} ================================================ FILE: apps/tools/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/tools/urls.py ================================================ from django.urls import path from . import views app_name = "tool" # @formatter:off urlpatterns = [ path('workspace/internal/tool', views.ToolView.InternalTool.as_view()), path('workspace/store/tool', views.ToolView.StoreTool.as_view()), path('workspace//tool', views.ToolView.as_view()), path('workspace//tool/workflow', views.ToolWorkflowView.as_view()), path('workspace//tool/import', views.ToolView.Import.as_view()), path('workspace//tool/pylint', views.ToolView.Pylint.as_view()), path('workspace//tool/debug', views.ToolView.Debug.as_view()), path('workspace//tool/tool_list', views.ToolView.Query.as_view()), path('workspace//tool/test_connection', views.ToolView.TestConnection.as_view()), path('workspace//tool/upload_skill_file', views.ToolView.UploadSkillFile.as_view()), path('workspace//tool/', views.ToolView.Operate.as_view()), path('workspace//tool//publish', views.ToolWorkflowView.Publish.as_view()), path('workspace//tool//debug', views.ToolWorkflowDebugView.as_view()), path('workspace//tool//workflow', views.ToolWorkflowView.Operate.as_view()), path('workspace//tool//workflow/export', views.ToolWorkflowView.Export.as_view()), path('workspace//tool//edit_icon', views.ToolView.EditIcon.as_view()), path('workspace//tool//export', views.ToolView.Export.as_view()), path('workspace//tool//add_internal_tool', views.ToolView.AddInternalTool.as_view()), path('workspace//tool//add_store_tool', views.ToolView.AddStoreTool.as_view()), path('workspace//tool//update_store_tool', views.ToolView.UpdateStoreTool.as_view()), path('workspace//tool//tool_record/', views.ToolView.ToolRecord.as_view()), path('workspace//tool//tool_record//', views.ToolView.PageToolRecord.as_view()), path('workspace//tool//', views.ToolView.Page.as_view()), path('workspace//tool//tool_version', views.ToolWorkflowVersionView.as_view()), path('workspace//tool//tool_version//', views.ToolWorkflowVersionView.Page.as_view()), path('workspace//tool//tool_version/', views.ToolWorkflowVersionView.Operate.as_view()), ] ================================================ FILE: apps/tools/views/__init__.py ================================================ from .tool import * from .tool_workflow import * from .tool_workflow_version import * ================================================ FILE: apps/tools/views/tool.py ================================================ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result from tools.api.tool import ToolCreateAPI, ToolEditAPI, ToolReadAPI, ToolDeleteAPI, ToolTreeReadAPI, ToolDebugApi, \ ToolExportAPI, ToolImportAPI, ToolPageAPI, PylintAPI, EditIconAPI, GetInternalToolAPI, AddInternalToolAPI from tools.models import ToolScope, Tool from tools.serializers.tool import ToolSerializer, ToolTreeSerializer def get_tool_operation_object(tool_id): tool_model = QuerySet(model=Tool).filter(id=tool_id).first() if tool_model is not None: return { "name": tool_model.name } return {} class ToolView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create tool'), summary=_('Create tool'), operation_id=_('Create tool'), # type: ignore parameters=ToolCreateAPI.get_parameters(), request=ToolCreateAPI.get_request(), responses=ToolCreateAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log( menu="Tool", operate="Create tool", get_operation_object=lambda r, k: r.data.get('name'), ) def post(self, request: Request, workspace_id: str): return result.success(ToolSerializer.Create( data={'user_id': request.user.id, 'workspace_id': workspace_id} ).insert({**request.data, 'scope': ToolScope.WORKSPACE})) @extend_schema( methods=['GET'], description=_('Get tool by folder'), summary=_('Get tool by folder'), operation_id=_('Get tool by folder'), # type: ignore parameters=ToolTreeReadAPI.get_parameters(), responses=ToolTreeReadAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def get(self, request: Request, workspace_id: str): return result.success(ToolTreeSerializer.Query( data={ 'workspace_id': workspace_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'scope': request.query_params.get('scope', ToolScope.WORKSPACE), 'tool_type': request.query_params.get('tool_type'), 'tool_type_list': request.query_params.getlist('tool_type_list[]'), 'user_id': request.user.id, 'create_user': request.query_params.get('create_user'), } ).get_tools()) class Debug(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Debug Tool'), summary=_('Debug Tool'), operation_id=_('Debug Tool'), # type: ignore request=ToolDebugApi.get_request(), responses=ToolDebugApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EDIT.get_workspace_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def post(self, request: Request, workspace_id: str): return result.success(ToolSerializer.Debug( data={'workspace_id': workspace_id, 'user_id': request.user.id} ).debug(request.data)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Update tool'), summary=_('Update tool'), operation_id=_('Update tool'), # type: ignore parameters=ToolEditAPI.get_parameters(), request=ToolEditAPI.get_request(), responses=ToolEditAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) @log( menu='Tool', operate='Update tool', get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def put(self, request: Request, workspace_id: str, tool_id: str): return result.success(ToolSerializer.Operate( data={'id': tool_id, 'workspace_id': workspace_id} ).edit(request.data)) @extend_schema( methods=['GET'], description=_('Get tool'), summary=_('Get tool'), operation_id=_('Get tool'), # type: ignore parameters=ToolReadAPI.get_parameters(), responses=ToolReadAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_tool_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), PermissionConstants.APPLICATION_READ.get_workspace_permission(), PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.USER.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) @log(menu='Tool', operate='Get tool') def get(self, request: Request, workspace_id: str, tool_id: str): return result.success(ToolSerializer.Operate( data={'id': tool_id, 'workspace_id': workspace_id} ).one()) @extend_schema( methods=['DELETE'], description=_('Delete tool'), summary=_('Delete tool'), operation_id=_('Delete tool'), # type: ignore parameters=ToolDeleteAPI.get_parameters(), responses=ToolDeleteAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_DELETE.get_workspace_tool_permission(), PermissionConstants.TOOL_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) @log( menu='Tool', operate="Delete tool", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def delete(self, request: Request, workspace_id: str, tool_id: str): return result.success(ToolSerializer.Operate( data={'id': tool_id, 'workspace_id': workspace_id} ).delete()) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get tool list by pagination'), summary=_('Get tool list by pagination'), operation_id=_('Get tool list by pagination'), # type: ignore parameters=ToolPageAPI.get_parameters(), responses=ToolPageAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log(menu='Tool', operate='Get tool list') def get(self, request: Request, workspace_id: str, current_page: int, page_size: int): return result.success(ToolTreeSerializer.Query( data={ 'workspace_id': workspace_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'scope': request.query_params.get('scope'), 'tool_type': request.query_params.get('tool_type'), 'user_id': request.user.id, 'create_user': request.query_params.get('create_user'), } ).page_tool_with_folders(current_page, page_size)) class Query(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get tool list '), summary=_('Get tool list'), operation_id=_('Get tool list'), # type: ignore parameters=ToolReadAPI.get_parameters(), responses=ToolReadAPI.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log(menu='Tool', operate='Get tool list') def get(self, request: Request, workspace_id: str): return result.success(ToolSerializer.Query( data={ 'workspace_id': workspace_id, 'folder_id': request.query_params.get('folder_id'), 'name': request.query_params.get('name'), 'scope': request.query_params.get('scope'), 'tool_type': request.query_params.get('tool_type'), 'user_id': request.user.id, 'create_user': request.query_params.get('create_user'), } ).get_tools()) class Import(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['POST'], description=_("Import tool"), summary=_("Import tool"), operation_id=_("Import tool"), # type: ignore parameters=ToolImportAPI.get_parameters(), request=ToolImportAPI.get_request(), responses=ToolImportAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_IMPORT.get_workspace_permission(), PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) @log(menu='Tool', operate='Import tool', ) def post(self, request: Request, workspace_id: str): return result.success(ToolSerializer.Import( data={ 'workspace_id': workspace_id, 'file': request.FILES.get('file'), 'user_id': request.user.id, 'folder_id': request.data.get('folder_id') } ).import_(ToolScope.WORKSPACE)) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Export tool"), summary=_("Export tool"), operation_id=_("Export tool"), # type: ignore parameters=ToolExportAPI.get_parameters(), responses=ToolExportAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(), PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) @log( menu='Tool', operate="Export tool", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def get(self, request: Request, tool_id: str, workspace_id: str): return ToolSerializer.Operate( data={'id': tool_id, 'workspace_id': workspace_id} ).export() class Pylint(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], summary=_('Check code'), operation_id=_('Check code'), # type: ignore description=_('Check code'), request=PylintAPI.get_request(), responses=PylintAPI.get_response(), parameters=PylintAPI.get_parameters(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def post(self, request: Request, workspace_id: str): return result.success(ToolSerializer.Pylint( data={'workspace_id': workspace_id} ).run(request.data)) class EditIcon(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['PUT'], summary=_('Edit tool icon'), operation_id=_('Edit tool icon'), # type: ignore description=_('Edit tool icon'), request=EditIconAPI.get_request(), responses=EditIconAPI.get_response(), parameters=EditIconAPI.get_parameters(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) def put(self, request: Request, tool_id: str, workspace_id: str): return result.success(ToolSerializer.IconOperate(data={ 'id': tool_id, 'workspace_id': workspace_id, 'user_id': request.user.id, 'image': request.FILES.get('file') }).edit(request.data)) class TestConnection(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Test tool connection"), summary=_("Test tool connection"), operation_id=_("Test tool connection"), # type: ignore request=ToolReadAPI.get_request(), responses=ToolReadAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), PermissionConstants.TOOL_EDIT.get_workspace_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def post(self, request: Request, workspace_id: str): return result.success(ToolSerializer.TestConnection(data={ 'workspace_id': workspace_id, 'code': request.data.get('code'), }).test_connection()) class InternalTool(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get internal tool"), summary=_("Get internal tool"), operation_id=_("Get internal tool"), # type: ignore parameters=GetInternalToolAPI.get_parameters(), responses=GetInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) def get(self, request: Request): return result.success(ToolSerializer.InternalTool(data={ 'user_id': request.user.id, 'name': request.query_params.get('name', ''), }).get_internal_tools()) class AddInternalTool(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Add internal tool"), summary=_("Add internal tool"), operation_id=_("Add internal tool"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), request=AddInternalToolAPI.get_request(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), ) @log( menu='Tool', operate="Add internal tool", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def post(self, request: Request, tool_id: str, workspace_id: str): return result.success(ToolSerializer.AddInternalTool(data={ 'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id }).add(request.data)) class StoreTool(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get Appstore tools"), summary=_("Get Appstore tools"), operation_id=_("Get Appstore tools"), # type: ignore responses=GetInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) def get(self, request: Request): return result.success(ToolSerializer.StoreTool(data={ 'user_id': request.user.id, 'name': request.query_params.get('name', ''), }).get_appstore_tools()) class AddStoreTool(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Add Appstore tool"), summary=_("Add Appstore tool"), operation_id=_("Add Appstore tool"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), request=AddInternalToolAPI.get_request(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), ) @log( menu='Tool', operate="Add Appstore tool", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def post(self, request: Request, tool_id: str, workspace_id: str): return result.success(ToolSerializer.AddStoreTool(data={ 'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).add(request.data)) class UpdateStoreTool(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_("Update Appstore tool"), summary=_("Update Appstore tool"), operation_id=_("Update Appstore tool"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), request=AddInternalToolAPI.get_request(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(), ) @log( menu='Tool', operate="Update Appstore tool", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def post(self, request: Request, tool_id: str, workspace_id: str): return result.success(ToolSerializer.UpdateStoreTool(data={ 'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id, 'download_url': request.data.get('download_url'), 'download_callback_url': request.data.get('download_callback_url'), 'icon': request.data.get('icon'), 'versions': request.data.get('versions'), }).update_tool(request.data)) class PageToolRecord(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get tool records"), summary=_("Get tool records"), operation_id=_("Get tool records"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_tool_permission(), PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) def get(self, request: Request, tool_id: str, workspace_id: str, current_page: int, page_size: int): return result.success(ToolSerializer.ToolRecord(data={ 'tool_id': tool_id, 'workspace_id': workspace_id, 'source_name': request.query_params.get('source_name'), 'source_type': request.query_params.get('source_type'), 'state': request.query_params.get('state'), }).get_tool_records(current_page, page_size)) class ToolRecord(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get tool record"), summary=_("Get tool record"), operation_id=_("Get tool record"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_tool_permission(), PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND), ) def get(self, request: Request, tool_id: str, workspace_id: str, record_id: str): return result.success(ToolSerializer.ToolRecord.Operate(data={ 'tool_id': tool_id, 'workspace_id': workspace_id, 'id': record_id, }).one()) class UploadSkillFile(APIView): authentication_classes = [TokenAuth] parser_classes = [MultiPartParser] @extend_schema( methods=['PUT'], description=_("Upload skill file"), summary=_("Upload skill file"), operation_id=_("Upload skill file"), # type: ignore parameters=AddInternalToolAPI.get_parameters(), request=AddInternalToolAPI.get_request(), responses=AddInternalToolAPI.get_response(), tags=[_("Tool")] # type: ignore ) @has_permissions( PermissionConstants.TOOL_CREATE.get_workspace_permission(), PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) def put(self, request: Request, workspace_id: str): return result.success(ToolSerializer.UploadSkillFile(data={ 'workspace_id': workspace_id, 'user_id': request.user.id, 'file': request.FILES.get('file'), }).upload()) ================================================ FILE: apps/tools/views/tool_workflow.py ================================================ # coding=utf-8 from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions, get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result, DefaultResultSerializer from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer from tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi from tools.serializers.tool_workflow import ToolWorkflowSerializer from tools.views import get_tool_operation_object class ToolWorkflowView(APIView): authentication_classes = [TokenAuth] class Publish(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_("Publishing an tool"), summary=_("Publishing an tool"), operation_id=_("Publishing an tool"), # type: ignore parameters=ToolWorkflowApi.get_parameters(), request=None, responses=DefaultResultSerializer, tags=[_('Tool')] # type: ignore ) @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Tool', operate='Publishing an tool', get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id'))) def put(self, request: Request, workspace_id: str, tool_id: str): return result.success( ToolWorkflowSerializer.Operate( data={'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).publish()) class Export(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Export tool workflow'), summary=_('Export tool workflow'), operation_id=_('Export tool workflow'), # type: ignore parameters=ToolWorkflowExportApi.get_parameters(), request=None, responses=ToolWorkflowExportApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(), PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()], CompareConstants.AND ) ) @log(menu='Tool', operate="Export tool workflow", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def get(self, request: Request, workspace_id: str, tool_id: str): return ToolWorkflowSerializer.Export( data={'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id} ).export() class Import(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Import tool workflow'), summary=_('Import tool workflow'), operation_id=_('Import tool workflow'), # type: ignore parameters=ToolWorkflowImportApi.get_parameters(), request=ToolWorkflowImportApi.get_request(), responses=ToolWorkflowImportApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(), PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()], CompareConstants.AND ) ) @log(menu='Tool', operate="Import tool workflow", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool')), ) def post(self, request: Request, workspace_id: str, tool_id: str): is_import_tool = get_is_permissions(request, workspace_id=workspace_id)( PermissionConstants.TOOL_IMPORT.get_workspace_permission(), PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() ) return result.success(ToolWorkflowSerializer.Import(data={ 'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id }).import_({'file': request.FILES.get('file')}, is_import_tool)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Edit tool workflow'), summary=_('Edit tool workflow'), operation_id=_('Edit tool workflow'), # type: ignore parameters=ToolWorkflowApi.get_parameters(), request=ToolWorkflowApi.get_request(), responses=ToolWorkflowApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND ) ) @log( menu='Tool', operate="Modify tool workflow", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def put(self, request: Request, workspace_id: str, tool_id: str): return result.success(ToolWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} ).edit(request.data)) @extend_schema( methods=['GET'], description=_('Get tool workflow'), summary=_('Get tool workflow'), operation_id=_('Get tool workflow'), # type: ignore parameters=KnowledgeWorkflowApi.get_parameters(), responses=KnowledgeWorkflowApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_tool_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND ), ) def get(self, request: Request, workspace_id: str, tool_id: str): return result.success(ToolWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} ).one()) class KnowledgeWorkflowVersionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get tool workflow version list'), summary=_('Get tool workflow version list'), operation_id=_('Get tool workflow version list'), # type: ignore parameters=ToolWorkflowApi.get_parameters(), responses=ToolWorkflowApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_READ.get_workspace_tool_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND ), ) def get(self, request: Request, workspace_id: str, tool_id: str): return result.success(KnowledgeWorkflowSerializer.Operate( data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id} ).one()) class ToolWorkflowDebugView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('tool workflow debug'), summary=_('tool workflow debug'), operation_id=_('tool workflow debug'), # type: ignore parameters=ToolWorkflowApi.get_parameters(), responses=ToolWorkflowApi.get_response(), tags=[_('Tool')] # type: ignore ) @has_permissions( PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ViewPermission( [RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_tool_permission()], CompareConstants.AND ), ) def post(self, request: Request, workspace_id: str, tool_id: str): return ToolWorkflowSerializer.Operate( data={'workspace_id': workspace_id, 'tool_id': tool_id, 'user_id': request.user.id}).debug( request.data, request.user, True) ================================================ FILE: apps/tools/views/tool_workflow_version.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_version.py.py @date:2025/6/3 15:46 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from knowledge.api.knowledge_version import KnowledgeVersionListAPI, KnowledgeVersionPageAPI, \ KnowledgeVersionOperateAPI from knowledge.models import Knowledge from tools.serializers.tool_version import ToolWorkflowVersionSerializer from tools.views import get_tool_operation_object def get_knowledge_operation_object(knowledge_id): knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first() if knowledge_model is not None: return { 'name': knowledge_model.name } return {} class ToolWorkflowVersionView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the tool version list"), summary=_("Get the tool version list"), operation_id=_("Get the tool version list"), # type: ignore parameters=KnowledgeVersionListAPI.get_parameters(), responses=KnowledgeVersionListAPI.get_response(), tags=[_('Tool/Version')] # type: ignore ) @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id, tool_id: str): return result.success( ToolWorkflowVersionSerializer.Query( data={'workspace_id': workspace_id}).list( {'name': request.query_params.get("name"), 'tool_id': tool_id})) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get the list of tool versions by page"), summary=_("Get the list of tool versions by page"), operation_id=_("Get the list of tool versions by page"), # type: ignore parameters=KnowledgeVersionPageAPI.get_parameters(), responses=KnowledgeVersionPageAPI.get_response(), tags=[_('Tool/Version')] # type: ignore ) @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, tool_id: str, current_page: int, page_size: int): return result.success( ToolWorkflowVersionSerializer.Query( data={'workspace_id': workspace_id}).page( {'name': request.query_params.get("name"), 'tool_id': tool_id}, current_page, page_size)) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_("Get tool version details"), summary=_("Get tool version details"), operation_id=_("Get tool version details"), # type: ignore parameters=KnowledgeVersionOperateAPI.get_parameters(), responses=KnowledgeVersionOperateAPI.get_response(), tags=[_('Tool/Version')] # type: ignore ) @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(), PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str): return result.success( ToolWorkflowVersionSerializer.Operate( data={'user_id': request.user, 'workspace_id': workspace_id, 'tool_id': tool_id, 'tool_version_id': tool_version_id}).one()) @extend_schema( methods=['PUT'], description=_("Modify tool version information"), summary=_("Modify tool version information"), operation_id=_("Modify tool version information"), # type: ignore parameters=KnowledgeVersionOperateAPI.get_parameters(), request=None, responses=KnowledgeVersionOperateAPI.get_response(), tags=[_('Tool/Version')] # type: ignore ) @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_knowledge_permission(), PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], [PermissionConstants.TOOL.get_workspace_knowledge_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Tool', operate="Modify tool version information", get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')), ) def put(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str): return result.success( ToolWorkflowVersionSerializer.Operate( data={'tool_id': tool_id, 'workspace_id': workspace_id, 'tool_version_id': tool_version_id, 'user_id': request.user.id}).edit( request.data)) ================================================ FILE: apps/trigger/__init__.py ================================================ ================================================ FILE: apps/trigger/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/trigger/api/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2026/1/14 15:48 @desc: """ ================================================ FILE: apps/trigger/api/trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: trigger.py @date:2026/1/14 15:49 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from knowledge.serializers.common import BatchSerializer from trigger.serializers.task_source_trigger import TaskSourceTriggerEditRequest from trigger.serializers.trigger import TriggerCreateRequest, TriggerResponse, BatchActiveSerializer class TriggerQueryResponseSerializer(serializers.Serializer): id = serializers.UUIDField(required=True, help_text="触发器id", label='触发器id') workspace_id = serializers.CharField(required=True, help_text="触发器工作空间", label='触发器工作空间') name = serializers.CharField(required=True, help_text="触发器名称", label='触发器名称') desc = serializers.CharField(required=True, help_text="触发器描述", label="触发器描述") trigger_type = serializers.CharField(required=True, help_text="触发器类型", label="触发器类型") type = serializers.CharField(required=True, help_text="资源类型", label="资源类型") is_active = serializers.BooleanField(required=True, help_text="是否激活", label="是否激活") source_name = serializers.CharField(required=True, help_text="资源类型", label="资源类型") source_icon = serializers.CharField(required=True, help_text="资源图标", label="资源图标") create_time = serializers.CharField(required=True, help_text="创建时间", label="创建时间") update_time = serializers.CharField(required=True, help_text="修改时间", label="修改时间") class TriggerTaskRecordResponse(ResultSerializer): def get_data(self): return TriggerQueryResponseSerializer(many=True) class TriggerQueryAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="name", description="触发器名称", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="type", description="触发器类型", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="task", description="任务名称", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="is_active", description="启用状态", type=OpenApiTypes.STR, required=True, ), OpenApiParameter( name="create_user", description="创建者", type=OpenApiTypes.STR, required=True, ), ] @staticmethod def get_response(): return TriggerTaskRecordResponse class TriggerQueryPageAPI(APIMixin): @staticmethod def get_parameters(): return [TriggerQueryAPI.get_parameters(), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, )] @staticmethod def get_response(): return TriggerQueryAPI.get_response() class TriggerCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TriggerCreateRequest @staticmethod def get_response(): return TriggerResponse class TaskSourceTriggerCreateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_id", description="资源id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_type", description="资源类型", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TriggerCreateRequest @staticmethod def get_response(): return TriggerResponse class TriggerBatchDeleteAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ) ] @staticmethod def get_request(): return BatchSerializer class TriggerBatchActiveAPI(APIMixin): @staticmethod def get_request(): return BatchActiveSerializer class TriggerOperateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_id", description="触发器id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TriggerCreateRequest @staticmethod def get_response(): return TriggerResponse class RequestSE(serializers.Serializer): pass class TriggerEditAPI(APIMixin): @staticmethod def get_request(): return TriggerCreateRequest class TaskSourceTriggerAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_id", description="资源id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_type", description="资源类型", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return TriggerResponse class TaskSourceTriggerOperateAPI(APIMixin): @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_id", description="资源id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="source_type", description="资源类型", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_id", description="触发器id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_request(): return TaskSourceTriggerEditRequest ================================================ FILE: apps/trigger/api/trigger_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: trigger_task.py @date:2026/1/28 16:37 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from trigger.serializers.trigger_task import ChatRecordSerializerModel, TriggerTaskResponse class TriggerTaskRecordResultSerializer(serializers.Serializer): id = serializers.UUIDField(required=True, help_text="任务记录id", label='任务记录id') state = serializers.CharField(required=True, help_text="任务记录状态", label='任务记录状态') source_type = serializers.CharField(required=True, help_text="资源类型", label='资源类型') source_name = serializers.CharField(required=True, help_text="资源名称", label="资源名称") source_id = serializers.CharField(required=True, help_text="资源id", label="资源id") task_record_id = serializers.CharField(required=True, help_text="资源任务记录id", label="资源任务记录id") trigger_id = serializers.CharField(required=True, help_text="触发器id", label="触发器id") type = serializers.CharField(required=True, help_text="资源类型", label="资源类型") create_time = serializers.CharField(required=True, help_text="创建时间", label="创建时间") update_time = serializers.CharField(required=True, help_text="修改时间", label="修改时间") class TriggerTaskRecordResponse(ResultSerializer): def get_data(self): return TriggerTaskRecordResultSerializer(many=True) class TriggerTaskRecordExecutionDetailsResponse(ResultSerializer): def get_data(self): return ChatRecordSerializerModel() class TriggerTaskResultSerializer(ResultSerializer): def get_data(self): return TriggerTaskResponse(many=True) class TriggerTaskAPI(APIMixin): @staticmethod def get_system_parameters(): return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if not parameter.name == 'workspace_id'] @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_id", description="触发器id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return TriggerTaskResultSerializer class TriggerTaskRecordPageAPI(APIMixin): @staticmethod def get_system_parameters(): return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if not parameter.name == 'workspace_id'] @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_id", description="触发器id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="current_page", description=_("Current page"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, location='path', required=True, ), OpenApiParameter( name="name", description="任务名称", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="state", description="状态", type=OpenApiTypes.STR, location='query', required=True, ), OpenApiParameter( name="order", description="排序字段", type=OpenApiTypes.STR, location='query', required=True, ), ] @staticmethod def get_response(): return TriggerTaskRecordResponse class TriggerTaskRecordExecutionDetailsAPI(APIMixin): @staticmethod def get_system_parameters(): return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if not parameter.name == 'workspace_id'] @staticmethod def get_parameters(): return [ OpenApiParameter( name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_id", description="触发器id", type=OpenApiTypes.STR, location='path', required=True, ), OpenApiParameter( name="trigger_task_id", description="触发器任务id", type=OpenApiTypes.STR, location='path', required=True, ), ] @staticmethod def get_response(): return TriggerTaskRecordExecutionDetailsResponse ================================================ FILE: apps/trigger/apps.py ================================================ from django.apps import AppConfig class TriggerConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'trigger' ================================================ FILE: apps/trigger/handler/base_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: base_task.py @date:2026/1/14 19:03 @desc: """ from abc import ABC, abstractmethod class BaseTriggerTask(ABC): """ 任务执行器抽象 """ @abstractmethod def support(self, trigger_task, **kwargs): pass @abstractmethod def execute(self, trigger_task, **kwargs): pass ================================================ FILE: apps/trigger/handler/base_trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: Trigger.py @date:2026/1/14 18:45 @desc: """ from abc import ABC, abstractmethod class BaseTrigger(ABC): """ 触发器抽象 """ @abstractmethod def support(self, trigger, **kwargs): pass @abstractmethod def deploy(self, trigger, **kwargs): pass @abstractmethod def undeploy(self, trigger, **kwargs): pass @staticmethod @abstractmethod def execute(trigger, **kwargs): pass ================================================ FILE: apps/trigger/handler/impl/task/application_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_task.py @date:2026/1/14 19:14 @desc: """ import json import time import traceback import uuid_utils.compat as uuid from django.db.models import QuerySet from application.models import ChatUserType, Chat, ChatRecord, ChatSourceChoices, Application from chat.serializers.chat import ChatSerializers from common.utils.logger import maxkb_logger from knowledge.models.knowledge_action import State from trigger.handler.base_task import BaseTriggerTask from trigger.models import TaskRecord, TriggerTask def get_reference(fields, obj): for field in fields: value = obj.get(field) if value is None: return None else: obj = value return obj def conversion_custom_value(value, _type): if ['array', 'dict', 'float', 'int', 'boolean', 'any'].__contains__(_type): try: return json.loads(value) except Exception as e: pass return value def valid_value_type(value, _type): if _type == 'array': return isinstance(value, list) if _type == 'dict': return isinstance(value, dict) if _type == 'float': return isinstance(value, float) if _type == 'int': return isinstance(value, int) if _type == 'boolean': return isinstance(value, bool) return isinstance(value, str) def get_field_value(value, kwargs, _type, required, default_value, field): source = value.get('source') if source == 'custom': _value = value.get('value') if _value: _value = conversion_custom_value(_value, _type) else: if default_value: return default_value if required: raise Exception(f'{field} is required') else: return None else: _value = get_reference(value.get('value'), kwargs) valid = valid_value_type(_value, _type) if not valid: raise Exception(f'{field} type error') return _value def get_application_execute_parameters(parameter_setting, application_parameters_setting, kwargs): many_field = ['api_input_field_list', 'user_input_field_list'] parameters = {'form_data': {}} for key, value in application_parameters_setting.items(): setting = parameter_setting.get(key) if setting: if many_field.__contains__(key): for ck, cv in value.items(): _setting = setting.get(ck) if _setting: _value = get_field_value(_setting, kwargs, cv.get('type'), cv.get('required'), cv.get('default_value'), ck) parameters['form_data'][ck] = _value else: if cv.get('default_value'): parameters['form_data'][ck] = cv.get('default_value') else: if cv.get('required'): raise Exception(f'{ck} is required') else: value = get_field_value(setting, kwargs, value.get('type'), value.get('required'), value.get('default_value'), key) parameters['message' if key == 'question' else key] = value else: if value.get('default_value'): parameters['message' if key == 'question' else key] = value.get('default_value') else: if value.get('required'): raise Exception(f'{"message" if key == "question" else key} is required') return parameters def get_loop_workflow_node(node_list): result = [] for item in node_list: if item.get('type') == 'loop-node': for loop_item in item.get('loop_node_data') or []: for inner_item in loop_item.values(): result.append(inner_item) return result def get_workflow_state(details): node_list = details.values() all_node = [*node_list, *get_loop_workflow_node(node_list)] err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')]) if err: return State.FAILURE return State.SUCCESS def get_user_field_component_input_type(input_type): if input_type == "MultiRow": return 'array' if input_type == "SwitchInput": return 'boolean' return 'string' def get_application_parameters_setting(application): application_parameter_setting = {'question': { 'required': True, 'type': 'string' }} if application.type == 'SIMPLE': return application_parameter_setting else: base_node_list = [n for n in application.work_flow.get('nodes') if n.get('type') == "base-node"] if len(base_node_list) == 0: raise Exception('Incorrect application workflow information') base_node = base_node_list[0] api_input_field_list = base_node.get('properties').get('api_input_field_list') or [] api_input_field_list = {user_field.get('variable'): { 'required': user_field.get('is_required'), 'default_value': user_field.get('default_value'), 'type': 'string' } for user_field in api_input_field_list} user_input_field_list = base_node.get('properties').get('user_input_field_list') or [] user_input_field_list = {user_field.get('field'): { 'required': user_field.get('required'), 'default_value': user_field.get('default_value'), 'type': get_user_field_component_input_type(user_field.get('input_type')) } for user_field in user_input_field_list} application_parameter_setting['api_input_field_list'] = api_input_field_list application_parameter_setting['user_input_field_list'] = user_input_field_list node_data = base_node.get('properties').get('node_data') or {} file_upload_enable = node_data.get('file_upload_enable') if file_upload_enable: file_upload_setting = node_data.get('file_upload_setting') or {} for field in ['audio', 'document', 'image', 'other', 'video']: v = file_upload_setting.get(field) if v: application_parameter_setting[field + '_list'] = {'required': False, 'default_value': [], 'type': 'array'} return application_parameter_setting class ApplicationTask(BaseTriggerTask): def support(self, trigger_task, **kwargs): return trigger_task.get('source_type') == 'APPLICATION' def execute(self, trigger_task, **kwargs): parameter_setting = trigger_task.get('parameter') task_record_id = uuid.uuid7() start_time = time.time() try: application = QuerySet(Application).filter(id=trigger_task.get('source_id')).only('type', 'work_flow').first() if application is None: QuerySet(TriggerTask).filter(id=trigger_task.get('id')).delete() return application_id = trigger_task.get('source_id') chat_id = uuid.uuid7() chat_user_id = str(uuid.uuid7()) chat_record_id = str(uuid.uuid7()) TaskRecord(id=task_record_id, trigger_id=trigger_task.get('trigger'), trigger_task_id=trigger_task.get('id'), source_type="APPLICATION", source_id=application_id, task_record_id=chat_record_id, meta={'chat_id': chat_id}, state=State.STARTED).save() application_parameters_setting = get_application_parameters_setting(application) parameters = get_application_execute_parameters(parameter_setting, application_parameters_setting, kwargs) parameters['re_chat'] = False parameters['stream'] = True parameters['chat_record_id'] = chat_record_id message = parameters.get('message') ip_address = '-' if kwargs.get('body') is not None: ip_address = kwargs.get('body').get('ip_address') Chat.objects.get_or_create(id=chat_id, defaults={ 'application_id': application_id, 'abstract': message, 'chat_user_id': chat_user_id, 'chat_user_type': ChatUserType.ANONYMOUS_USER.value, 'asker': {'username': "游客"}, 'ip_address': ip_address, 'source': { 'type': ChatSourceChoices.TRIGGER.value }, }) list(ChatSerializers(data={ "chat_id": chat_id, "chat_user_id": chat_user_id, 'chat_user_type': ChatUserType.ANONYMOUS_USER.value, 'application_id': application_id, 'ip_address': ip_address, 'source': { 'type': ChatSourceChoices.TRIGGER.value }, 'debug': False }).chat(instance=parameters)) chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first() if chat_record: state = get_workflow_state(chat_record.details) QuerySet(TaskRecord).filter(id=task_record_id).update(state=state, run_time=chat_record.run_time, meta={'parameter_setting': parameter_setting, 'input': parameters, 'output': None}) else: QuerySet(TaskRecord).filter(id=task_record_id).update(state=State.FAILURE, run_time=time.time() - start_time, meta={'parameter_setting': parameter_setting, 'input': parameters, 'output': None, 'err_message': 'Error: An unknown error occurred during the execution of the conversation'}) except Exception as e: maxkb_logger.error(f"Application execution error: {traceback.format_exc()}") QuerySet(TaskRecord).filter(id=task_record_id).update( state=State.FAILURE, run_time=time.time() - start_time, meta={'input': {'parameter_setting': parameter_setting, **kwargs}, 'output': None, 'err_message': 'Error: ' + str(e)} ) ================================================ FILE: apps/trigger/handler/impl/task/tool_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: application_task.py @date:2026/1/14 19:14 @desc: """ import json import time import traceback import uuid_utils.compat as uuid from django.db.models import QuerySet from django.utils.translation import gettext as _ from common.utils.logger import maxkb_logger from common.utils.rsa_util import rsa_long_decrypt from common.utils.tool_code import ToolExecutor from knowledge.models.knowledge_action import State from tools.models import Tool, ToolRecord, ToolTaskTypeChoices from trigger.handler.base_task import BaseTriggerTask from trigger.models import TaskRecord executor = ToolExecutor() def get_reference(fields, obj): for field in fields: value = obj.get(field) if value is None: return None else: obj = value return obj def get_field_value(value, kwargs): source = value.get('source') if source == 'custom': return value.get('value') else: return get_reference(value.get('value'), kwargs) def _convert_value(_type, value): if value is None: return None if _type == 'int': return int(value) if _type == 'boolean': value = 0 if ['0', '[]'].__contains__(value) else value return bool(value) if _type == 'float': return float(value) if _type == 'dict': v = json.loads(value) if isinstance(v, dict): return v raise Exception(_('type error')) if _type == 'array': v = json.loads(value) if isinstance(v, list): return v raise Exception(_('type error')) return value def get_tool_execute_parameters(input_field_list, parameter_setting, kwargs): type_map = {f.get("name"): f.get("type") for f in (input_field_list or []) if f.get("name")} parameters = {} for key, value in parameter_setting.items(): raw = get_field_value(value, kwargs) parameters[key] = _convert_value(type_map.get(key), raw) return parameters def get_loop_workflow_node(node_list): result = [] for item in node_list: if item.get('type') == 'loop-node': for loop_item in item.get('loop_node_data') or []: for inner_item in loop_item.values(): result.append(inner_item) return result def get_workflow_state(details): node_list = details.values() all_node = [*node_list, *get_loop_workflow_node(node_list)] err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')]) if err: return State.FAILURE return State.SUCCESS def _get_result_detail(result): if isinstance(result, dict): result_dict = {k: (str(v)[:500] if len(str(v)) > 500 else v) for k, v in result.items()} elif isinstance(result, list): result_dict = [str(item)[:500] if len(str(item)) > 500 else item for item in result] elif isinstance(result, str): result_dict = result[:500] if len(result) > 500 else result else: result_dict = result return result_dict class ToolTask(BaseTriggerTask): def support(self, trigger_task, **kwargs): return trigger_task.get('source_type') == 'TOOL' def execute(self, trigger_task, **kwargs): parameter_setting = trigger_task.get('parameter') tool_id = trigger_task.get('source_id') task_record_id = uuid.uuid7() start_time = time.time() try: tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first() if not tool: maxkb_logger.info(f"Tool with id {tool_id} not found or inactive.") return TaskRecord( id=task_record_id, trigger_id=trigger_task.get('trigger'), trigger_task_id=trigger_task.get('id'), source_type="TOOL", source_id=tool_id, task_record_id=task_record_id, meta={'input': parameter_setting, 'output': {}}, state=State.STARTED ).save() ToolRecord( id=task_record_id, workspace_id=tool.workspace_id, tool_id=tool.id, source_type=ToolTaskTypeChoices.TRIGGER, source_id=trigger_task.get('trigger'), meta={'input': parameter_setting, 'output': {}}, state=State.STARTED ).save() parameters = get_tool_execute_parameters(tool.input_field_list, parameter_setting, kwargs) init_params_default_value = {i["field"]: i.get('default_value') for i in tool.init_field_list} if tool.init_params is not None: all_params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params)) | parameters else: all_params = init_params_default_value | parameters result = executor.exec_code(tool.code, all_params) result_dict = _get_result_detail(result) maxkb_logger.debug(f"Tool execution result: {result}") QuerySet(TaskRecord).filter(id=task_record_id).update( state=State.SUCCESS, run_time=time.time() - start_time, meta={'input': parameter_setting, 'output': result_dict} ) QuerySet(ToolRecord).filter(id=task_record_id).update( state=State.SUCCESS, run_time=time.time() - start_time, meta={'input': parameters, 'output': result_dict} ) except Exception as e: maxkb_logger.error(f"Tool execution error: {traceback.format_exc()}") QuerySet(TaskRecord).filter(id=task_record_id).update( state=State.FAILURE, run_time=time.time() - start_time, meta={'input': parameter_setting, 'output': 'Error: ' + str(e), 'err_message': 'Error: ' + str(e)} ) QuerySet(ToolRecord).filter(id=task_record_id).update( state=State.FAILURE, run_time=time.time() - start_time, meta={'input': parameter_setting, 'output': 'Error: ' + str(e), 'err_message': 'Error: ' + str(e)} ) ================================================ FILE: apps/trigger/handler/impl/trigger/event_trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: event_trigger.py @date:2026/1/15 11:08 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext as _, gettext_lazy from drf_spectacular.utils import extend_schema, OpenApiExample from rest_framework import serializers from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import WebhookAuth from common.exception.app_exception import AppApiException, AppAuthenticationFailed from common.log.log import _get_ip_address from common.result import Result from trigger.handler.base_trigger import BaseTrigger from trigger.models import TriggerTask, Trigger from trigger.serializers.trigger import TriggerResponse from trigger.serializers.trigger_task import TriggerTaskResponse def valid_parameter_type(value, _type, desc): try: if _type == 'int': instance_type = int | float elif _type == 'boolean': instance_type = bool elif _type == 'float': instance_type = float | int elif _type == 'dict': instance_type = dict elif _type == 'array': instance_type = list elif _type == 'string': instance_type = str else: raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Unsupported types' ).format(name=desc, _type=_type)) except: return value if not isinstance(value, instance_type): raise Exception(_( 'Field: {name} Type: {_type} Value: {value} Type error' ).format(name=desc, _type=_type, value=value)) return value def get_parameters(body_setting, request: Request): parameters = {} for body in body_setting: value = request.data.get(body.get('field')) required = body.get('required') if value is None and required: raise AppApiException(500, f'{body.get("desc")} is required') if value is None and not required: parameters[body.get('field')] = None continue _type = body.get('type') valid_parameter_type(value, _type, body.get("desc")) parameters[body.get('field')] = value ip_address = _get_ip_address(request) parameters['ip_address'] = ip_address or '-' return parameters class EventTriggerRequest(serializers.Serializer): pass class EventTriggerView(APIView): authentication_classes = [WebhookAuth] @extend_schema( methods=['POST'], description=gettext_lazy('Event Trigger WebHook'), summary=gettext_lazy('Event Trigger WebHook'), operation_id=gettext_lazy('Event Trigger WebHook'), # type: ignore request={ 'application/json': { 'schema': { 'type': 'object', 'example': {} } } }, tags=[gettext_lazy('Trigger')], # type: ignore examples=[ OpenApiExample( 'Example Request', description='Send an empty JSON object as request body', value={}, request_only=True, # 仅用于请求示例 response_only=False, ) ] ) def post(self, request: Request, trigger_id: str): trigger = QuerySet(Trigger).filter(id=trigger_id).first() if trigger: return EventTrigger.execute(TriggerResponse(trigger).data, request) return Result(code=404, message="404") class EventTrigger(BaseTrigger): """ 事件触发器 """ @staticmethod def execute(trigger, request=None, **kwargs): trigger_setting = trigger.get('trigger_setting') if trigger_setting.get('token'): token = request.META.get('HTTP_AUTHORIZATION') if trigger_setting.get('token') != token.replace('Bearer ', ''): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) is_active = trigger.get('is_active') if not is_active: return Result(code=404, message="404", response_status=404) body = trigger_setting.get('body') parameters = get_parameters(body, request) trigger_task_list = [TriggerTaskResponse(trigger_task).data for trigger_task in QuerySet(TriggerTask).filter(trigger__id=trigger.get('id'), is_active=True)] from trigger.handler.simple_tools import execute for trigger_task in trigger_task_list: execute(trigger_task, body=parameters) return result.success(True) def support(self, trigger, **kwargs): return trigger.get('trigger_type') == 'EVENT' def deploy(self, trigger, **kwargs): return True def undeploy(self, trigger, **kwargs): return True ================================================ FILE: apps/trigger/handler/impl/trigger/scheduled_trigger.py ================================================ # coding=utf-8 from django.db.models import QuerySet from common.utils.logger import maxkb_logger from ops import celery_app from trigger.handler.base_trigger import BaseTrigger from trigger.models import TriggerTask def _parse_hhmm(value: str) -> tuple[int, int]: hour_str, minute_str = (value or "").split(":") hour = int(hour_str) minute = int(minute_str) if not (0 <= hour <= 23 and 0 <= minute <= 59): raise ValueError("hour/minute out of range") return hour, minute def _weekday_to_cron(d: int | str) -> str: mapping = {1: "mon", 2: "tue", 3: "wed", 4: "thu", 5: "fri", 6: "sat", 7: "sun", 0: "sun"} di = int(d) if di not in mapping: raise ValueError("invalid weekday") return mapping[di] def _get_active_trigger_tasks(trigger_id: str) -> list[dict]: return list( QuerySet(TriggerTask) .filter(trigger_id=trigger_id, is_active=True) .values("id", "source_type", "source_id", "parameter", "trigger") ) def _deploy_daily(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler times = setting.get("time") or [] for t in times: try: hour, minute = _parse_hhmm(t) except Exception: maxkb_logger.warning(f"invalid time={t}, trigger_id={trigger_id}") continue for task in trigger_tasks: job_id = f"trigger:{trigger_id}:task:{task['id']}:daily:{hour:02d}{minute:02d}" scheduler.add_job( ScheduledTrigger.execute, trigger="cron", hour=str(hour), minute=str(minute), id=job_id, kwargs={"trigger": trigger, "trigger_task": task}, replace_existing=True, ) def _deploy_weekly(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler times = setting.get("time") or [] days = setting.get("days") or [] if not times or not days: maxkb_logger.warning(f"empty weekly setting, trigger_id={trigger_id}") return for d in days: try: dow = _weekday_to_cron(d) except Exception: maxkb_logger.warning(f"invalid weekday={d}, trigger_id={trigger_id}") continue for t in times: try: hour, minute = _parse_hhmm(t) except Exception: maxkb_logger.warning(f"invalid time={t}, trigger_id={trigger_id}") continue for task in trigger_tasks: job_id = f"trigger:{trigger_id}:task:{task['id']}:weekly:{dow}:{hour:02d}{minute:02d}" scheduler.add_job( ScheduledTrigger.execute, trigger="cron", day_of_week=dow, hour=str(hour), minute=str(minute), id=job_id, kwargs={"trigger": trigger, "trigger_task": task}, replace_existing=True, ) def _deploy_monthly(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler times = setting.get("time") or [] days = setting.get("days") or [] if not times or not days: maxkb_logger.warning(f"empty monthly setting, trigger_id={trigger_id}") return for d in days: try: dom = int(d) if not (1 <= dom <= 31): raise ValueError("invalid day of month") except Exception: maxkb_logger.warning(f"invalid day={d}, trigger_id={trigger_id}") continue for t in times: try: hour, minute = _parse_hhmm(t) except Exception: maxkb_logger.warning(f"invalid time={t}, trigger_id={trigger_id}") continue for task in trigger_tasks: job_id = f"trigger:{trigger_id}:task:{task['id']}:monthly:{dom:02d}:{hour:02d}{minute:02d}" scheduler.add_job( ScheduledTrigger.execute, trigger="cron", day=str(dom), hour=str(hour), minute=str(minute), id=job_id, kwargs={"trigger": trigger, "trigger_task": task}, replace_existing=True, ) def _deploy_cron(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler from apscheduler.triggers.cron import CronTrigger cron_expression = setting.get('cron_expression') if not cron_expression: maxkb_logger.warning(f"empty cron_expression, trigger_id={trigger_id}") return try: cron_trigger = CronTrigger.from_crontab(cron_expression.strip()) except ValueError: maxkb_logger.warning(f"invalid cron_expression={cron_expression}, trigger_id={trigger_id}") return for task in trigger_tasks: job_id = f"trigger:{trigger_id}:task:{task['id']}:cron:{cron_expression.strip()}" scheduler.add_job( ScheduledTrigger.execute, trigger=cron_trigger, id=job_id, kwargs={"trigger": trigger, "trigger_task": task}, replace_existing=True, ) def _deploy_interval(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler unit = (setting.get("interval_unit") or "").strip() value = setting.get("interval_value") try: value_i = int(value) if value_i <= 0: raise ValueError("interval_value must be positive") except Exception: maxkb_logger.warning(f"invalid interval_value={value}, trigger_id={trigger_id}") return if unit not in {"seconds", "minutes", "hours", "days"}: maxkb_logger.warning(f"invalid interval_unit={unit}, trigger_id={trigger_id}") return for task in trigger_tasks: job_id = f"trigger:{trigger_id}:task:{task['id']}:interval:{unit}:{value_i}" scheduler.add_job( ScheduledTrigger.execute, trigger="interval", id=job_id, kwargs={"trigger": trigger, "trigger_task": task}, replace_existing=True, **{unit: value_i}, ) @celery_app.task(name="celery:undeploy_scheduled_trigger") def _remove_trigger_jobs(trigger_id: str) -> None: from common.job import scheduler prefix = f"trigger:{trigger_id}:" for job in scheduler.get_jobs(): if getattr(job, "id", "").startswith(prefix): try: job.remove() except Exception as e: maxkb_logger.warning(f"remove job failed, job_id={job.id}, err={e}") @celery_app.task(name="celery:deploy_scheduled_trigger") def deploy_scheduled_trigger(trigger: dict, trigger_tasks: list[dict], setting: dict, schedule_type: str) -> None: _remove_trigger_jobs(trigger["id"]) deployers = { "daily": _deploy_daily, "weekly": _deploy_weekly, "monthly": _deploy_monthly, "interval": _deploy_interval, 'cron': _deploy_cron } fn = deployers.get(schedule_type) if not fn: maxkb_logger.warning(f"unsupported schedule_type={schedule_type}, trigger_id={trigger['id']}") return fn(trigger, trigger_tasks, setting, trigger["id"]) class ScheduledTrigger(BaseTrigger): """ 定时任务触发器 """ @staticmethod def execute(trigger, **kwargs): trigger_task = kwargs.get("trigger_task") if not trigger_task: maxkb_logger.warning(f"unsupported task={trigger_task}") return source_type = trigger_task["source_type"] if source_type == "APPLICATION": from trigger.handler.impl.task.application_task import ApplicationTask ApplicationTask.execute(trigger_task, **kwargs) elif source_type == "TOOL": from trigger.handler.impl.task.tool_task import ToolTask ToolTask.execute(trigger_task, **kwargs) else: maxkb_logger.warning(f"unsupported source_type={source_type}, task_id={trigger_task['id']}") def support(self, trigger, **kwargs): return trigger.get("trigger_type") == "SCHEDULED" def deploy(self, trigger, **kwargs): trigger_id = str(trigger["id"]) setting = trigger.get("trigger_setting") or {} schedule_type = setting.get("schedule_type") if not trigger.get("is_active", True): self.undeploy(trigger, **kwargs) return if trigger.get("trigger_type") != "SCHEDULED": self.undeploy(trigger, **kwargs) return trigger_tasks = _get_active_trigger_tasks(trigger["id"]) if not trigger_tasks: maxkb_logger.warning(f"no active trigger_tasks, trigger_id={trigger_id}") self.undeploy(trigger, **kwargs) return deploy_scheduled_trigger.delay(trigger, trigger_tasks, setting, schedule_type) def undeploy(self, trigger, **kwargs): trigger_id = str(trigger["id"]) _remove_trigger_jobs.delay(trigger_id) ================================================ FILE: apps/trigger/handler/simple_tools.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: simple_task.py @date:2026/1/14 19:18 @desc: """ from threading import Thread from trigger.handler.impl.task.application_task import ApplicationTask from trigger.handler.impl.task.tool_task import ToolTask from trigger.handler.impl.trigger.event_trigger import EventTrigger from trigger.handler.impl.trigger.scheduled_trigger import ScheduledTrigger simple_task_handlers = [ApplicationTask(), ToolTask()] simple_trigger_handlers = [ScheduledTrigger(), EventTrigger()] def execute(trigger_task, **kwargs): """ 执行触发器任务 @param trigger_task: 触发器任务数据 @param kwargs: 额外数据 @return: """ for simple_task_handler in simple_task_handlers: if simple_task_handler.support(trigger_task, **kwargs): Thread(target=simple_task_handler.execute, args=(trigger_task,), kwargs=kwargs).start() return raise Exception("不支持的处理器类型") def deploy(trigger, **kwargs): """ 部署触发器 @param trigger: 触发器字典数据 @param kwargs: 额外数据 @return: """ for simple_trigger_handler in simple_trigger_handlers: if simple_trigger_handler.support(trigger, **kwargs): return simple_trigger_handler.deploy(trigger, **kwargs) raise Exception("不支持的触发器类型") def undeploy(trigger, **kwargs): """ 取消部署触发器 @param trigger: 触发器字典数据 @param kwargs: 额外数据 @return: """ for simple_trigger_handler in simple_trigger_handlers: return simple_trigger_handler.undeploy(trigger, **kwargs) raise Exception("不支持的触发器类型") ================================================ FILE: apps/trigger/migrations/0001_initial.py ================================================ # Generated by Django 5.2.9 on 2026-01-23 09:47 import common.encoder.encoder import django.db.models.deletion import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ('users', '0001_initial'), ] operations = [ migrations.CreateModel( name='Trigger', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('name', models.CharField(db_index=True, max_length=128, verbose_name='触发器名称')), ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')), ('trigger_type', models.CharField(choices=[('SCHEDULED', 'Scheduled'), ('EVENT', 'Event')], default='SCHEDULED', max_length=256, verbose_name='触发器类型')), ('trigger_setting', models.JSONField(default=dict)), ('meta', models.JSONField(default=dict)), ('is_active', models.BooleanField(db_index=True, default=True)), ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), ], options={ 'db_table': 'event_trigger', }, ), migrations.CreateModel( name='TriggerTask', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('TOOL', 'Tool')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')), ('source_id', models.UUIDField(verbose_name='资源id')), ('is_active', models.BooleanField(db_index=True, default=True)), ('parameter', models.JSONField(default=list)), ('meta', models.JSONField(default=dict)), ('trigger', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.trigger')), ], options={ 'db_table': 'event_trigger_task', 'unique_together': {('trigger', 'source_id', 'source_type')}, }, ), migrations.CreateModel( name='TaskRecord', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('TOOL', 'Tool')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')), ('source_id', models.UUIDField(verbose_name='资源id')), ('task_record_id', models.UUIDField(verbose_name='任务记录id')), ('meta', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder)), ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')), ('run_time', models.FloatField(default=0, verbose_name='运行时长')), ('trigger', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.trigger')), ('trigger_task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.triggertask')), ], options={ 'db_table': 'event_trigger_task_record', }, ), ] ================================================ FILE: apps/trigger/migrations/0002_remove_taskrecord_trigger_task_and_more.py ================================================ # Generated by Django 5.2.9 on 2026-01-26 03:27 import uuid_utils.compat from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('trigger', '0001_initial'), ] operations = [ migrations.RemoveField( model_name='taskrecord', name='trigger_task', ), migrations.AddField( model_name='taskrecord', name='trigger_task_id', field=models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, verbose_name='触发器任务id'), ), ] ================================================ FILE: apps/trigger/migrations/__init__.py ================================================ ================================================ FILE: apps/trigger/models/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2026/1/9 16:13 @desc: """ from .trigger import * ================================================ FILE: apps/trigger/models/trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: trigger.py.py @date:2026/1/9 15:33 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.encoder.encoder import SystemEncoder from common.mixins.app_model_mixin import AppModelMixin from knowledge.models.knowledge_action import State from users.models import User class TriggerTypeChoices(models.TextChoices): SCHEDULED = 'SCHEDULED' EVENT = 'EVENT' class TriggerTaskTypeChoices(models.TextChoices): APPLICATION = 'APPLICATION' TOOL = 'TOOL' class Trigger(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) name = models.CharField(max_length=128, verbose_name="触发器名称", db_index=True) desc = models.CharField(max_length=512, verbose_name="引用描述", default="") trigger_type = models.CharField(verbose_name="触发器类型", choices=TriggerTypeChoices.choices, default=TriggerTypeChoices.SCHEDULED, max_length=256) trigger_setting = models.JSONField(default=dict) meta = models.JSONField(default=dict) is_active = models.BooleanField(default=True, db_index=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) class Meta: db_table = "event_trigger" class TriggerTask(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") trigger = models.ForeignKey(Trigger, on_delete=models.CASCADE) source_type = models.CharField(verbose_name="触发器任务类型", choices=TriggerTaskTypeChoices.choices, default=TriggerTaskTypeChoices.APPLICATION, max_length=256 ) source_id = models.UUIDField(verbose_name="资源id") is_active = models.BooleanField(default=True, db_index=True) parameter = models.JSONField(default=list) meta = models.JSONField(default=dict) class Meta: unique_together = [('trigger', 'source_id', 'source_type')] db_table = "event_trigger_task" class TaskRecord(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") trigger = models.ForeignKey(Trigger, on_delete=models.CASCADE) trigger_task_id = models.UUIDField(max_length=128, default=uuid.uuid7, editable=False, verbose_name="触发器任务id") source_type = models.CharField(verbose_name="触发器任务类型", choices=TriggerTaskTypeChoices.choices, default=TriggerTaskTypeChoices.APPLICATION, max_length=256) source_id = models.UUIDField(verbose_name="资源id") task_record_id = models.UUIDField(verbose_name="任务记录id") meta = models.JSONField(default=dict, encoder=SystemEncoder) state = models.CharField(verbose_name='状态', max_length=20, choices=State.choices, default=State.STARTED) run_time = models.FloatField(verbose_name="运行时长", default=0) class Meta: db_table = "event_trigger_task_record" ================================================ FILE: apps/trigger/serializers/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2026/1/9 16:16 @desc: """ ================================================ FILE: apps/trigger/serializers/task_source_trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: task_source_trigger.py @date:2026/1/22 16:18 @desc: """ from typing import Dict from django.db import transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application from common.exception.app_exception import AppApiException from tools.models import Tool from trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask from trigger.serializers.trigger import TriggerModelSerializer, TriggerSerializer, ApplicationTriggerTaskSerializer, \ ToolTriggerTaskSerializer, TriggerTaskModelSerializer class TaskSourceTriggerTaskEditRequest(serializers.Serializer): meta = serializers.DictField(default=dict, required=False) parameter = serializers.DictField(default=dict, required=False) class TaskSourceTriggerEditRequest(serializers.Serializer): name = serializers.CharField(required=False, label=_('trigger name')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description')) trigger_type = serializers.ChoiceField(required=False, choices=TriggerTypeChoices) trigger_setting = serializers.DictField(required=False, label=_("trigger setting")) meta = serializers.DictField(default=dict, required=False) trigger_task = TaskSourceTriggerTaskEditRequest(many=True, required=False) class TaskSourceTriggerSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) def insert(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) if not len(instance.get("trigger_task")) == 1: raise AppApiException(500, _('Trigger task number must be one')) source_id = instance.get('source_id') source_type = instance.get('source_type') source_trigger_task = instance.get('trigger_task')[0] if not (instance.get('source_id') == source_id and source_trigger_task.get('source_type') == source_type): raise AppApiException(500, _('Incorrect trigger task')) return TriggerSerializer(data={ 'workspace_id': self.data.get('workspace_id'), 'user_id': self.data.get('user_id') }).insert(instance, with_valid=True) class TaskSourceTriggerOperateSerializer(serializers.Serializer): trigger_id = serializers.UUIDField(required=True, label=_('trigger id')) workspace_id = serializers.CharField(required=False, label=_('workspace id')) source_type = serializers.CharField(required=True, label=_('source type')) source_id = serializers.CharField(required=True, label=_('source id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Trigger id does not exist')) def one(self, with_valid=True): if with_valid: self.is_valid() trigger_id = self.data.get('trigger_id') workspace_id = self.data.get('workspace_id') source_id = self.data.get('source_id') source_type = self.data.get('source_type') trigger = QuerySet(Trigger).filter(workspace_id=workspace_id, id=trigger_id).first() trigger_task = TriggerTaskModelSerializer(TriggerTask.objects.filter( trigger_id=trigger_id, source_id=source_id, source_type=source_type).first()).data if source_type == TriggerTaskTypeChoices.APPLICATION: application_task = ApplicationTriggerTaskSerializer( Application.objects.filter(workspace_id=workspace_id, id=source_id).first()).data return { **TriggerModelSerializer(trigger).data, 'trigger_task': trigger_task, 'application_task': application_task, } if source_type == TriggerTaskTypeChoices.TOOL: tool_task = ToolTriggerTaskSerializer( Tool.objects.filter(workspace_id=workspace_id, id=source_id).first()).data return { **TriggerModelSerializer(trigger).data, 'trigger_task': trigger_task, 'tool_task': tool_task, } @transaction.atomic def edit(self, instance: Dict, with_valid=True): from trigger.handler.simple_tools import deploy, undeploy if with_valid: self.is_valid(raise_exception=True) serializer = TaskSourceTriggerEditRequest(data=instance) serializer.is_valid(raise_exception=True) valid_data = serializer.validated_data trigger_id = self.data.get('trigger_id') workspace_id = self.data.get('workspace_id') source_id = self.data.get('source_id') source_type = self.data.get('source_type') trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first() if not trigger: raise serializers.ValidationError(_('Trigger not found')) task_source_trigger_edit_field_list = ['name', 'desc', 'trigger_type', 'trigger_setting', 'meta'] trigger_deploy_edit_field_list = ['trigger_type', 'trigger_setting'] need_redeploy = any(field in instance for field in trigger_deploy_edit_field_list) for field in task_source_trigger_edit_field_list: if field in valid_data: setattr(trigger, field, valid_data.get(field)) trigger.save() trigger_task = valid_data.get('trigger_task') if trigger_task is not None: # 检查是否为空列表 if not trigger_task: raise serializers.ValidationError(_('Trigger must have at least one task')) TriggerTask.objects.filter( source_id=source_id, source_type=source_type, trigger_id=trigger_id ).update(parameter=trigger_task[0].get("parameter"), meta=trigger_task[0].get("meta")) else: # 用户没提交 trigger_task 字段,确保数据库中有 task if not TriggerTask.objects.filter(trigger_id=trigger_id).exists(): raise serializers.ValidationError(_('Trigger must have at least one task')) if need_redeploy: if trigger.is_active and trigger.trigger_type == 'SCHEDULED': deploy(TriggerModelSerializer(trigger).data, **{}) else: undeploy(TriggerModelSerializer(trigger).data, **{}) return self.one() # 删除的是当前trigger_id+source_id+source_type对应的task @transaction.atomic def delete(self): from trigger.handler.simple_tools import undeploy self.is_valid(raise_exception=True) trigger_id = self.data.get('trigger_id') workspace_id = self.data.get('workspace_id') source_id = self.data.get('source_id') source_type = self.data.get('source_type') trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first() if not trigger: raise AppApiException(404, _('Trigger not found')) delete_count = TriggerTask.objects.filter(trigger_id=trigger_id, source_id=source_id, source_type=source_type).delete()[0] if delete_count == 0: raise AppApiException(404, _('Task not found')) has_other_tasks = TriggerTask.objects.filter(trigger_id=trigger_id).exists() undeploy(TriggerModelSerializer(trigger).data, **{}) if not has_other_tasks: trigger.delete() return True class TaskSourceTriggerListSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) source_type = serializers.CharField(required=True, label=_('source type')) source_id = serializers.CharField(required=True, label=_('source id')) def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) triggers = Trigger.objects.filter(workspace_id=self.data.get("workspace_id"), triggertask__source_id=self.data.get("source_id"), triggertask__source_type=self.data.get("source_type"), is_active=True ).distinct() return [TriggerModelSerializer(trigger).data for trigger in triggers] ================================================ FILE: apps/trigger/serializers/trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: trigger.py @date:2026/1/14 11:48 @desc: """ import os.path import re from typing import Dict import uuid_utils.compat as uuid from django.core import validators from django.db import models, transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application from common.db.search import get_dynamics_model, native_page_search, native_search from common.exception.app_exception import AppApiException from common.field.common import ObjectField from common.utils.common import get_file_content from knowledge.serializers.common import BatchSerializer from maxkb.conf import PROJECT_DIR from tools.models import Tool from trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask, TaskRecord class BatchActiveSerializer(serializers.Serializer): id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list')) is_active = serializers.BooleanField(required=True, label=_("is_active")) def is_valid(self, *, model=None, raise_exception=False): super().is_valid(raise_exception=True) if model is not None: id_list = self.data.get('id_list') model_list = QuerySet(model).filter(id__in=id_list) if len(model_list) != len(id_list): model_id_list = [str(m.id) for m in model_list] error_id_list = list(filter(lambda row_id: not model_id_list.__contains__(row_id), id_list)) raise AppApiException(500, _('The following id does not exist: %s') % ','.join(map(str, error_id_list))) class InputField(serializers.Serializer): source = serializers.CharField(required=True, label=_("source"), validators=[ validators.RegexValidator(regex=re.compile("^custom|reference$"), message=_("The field only supports custom|reference"), code=500) ]) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list]) class ApplicationTaskParameterSerializer(serializers.Serializer): question = InputField(required=True) api_input_field_list = serializers.JSONField(required=False) user_input_field_list = serializers.JSONField(required=False) image_list = InputField(required=False) document_list = InputField(required=False) audio_list = InputField(required=False) video_list = InputField(required=False) other_list = InputField(required=False) @staticmethod def _validate_input_dict(value, field_name): if not value: return value if not isinstance(value, dict): raise serializers.ValidationError(_("%s must be a dict") % field_name) for key, val in value.items(): serializer = InputField(data=val) if not serializer.is_valid(): raise serializers.ValidationError({f"{field_name}.{key}": serializer.errors}) return value def validate_api_input_field_list(self, value): return self._validate_input_dict(value, 'api_input_field_list') def validate_user_input_field_list(self, value): return self._validate_input_dict(value, 'user_input_field_list') class ToolTaskParameterSerializer(serializers.Serializer): def to_internal_value(self, data): if not isinstance(data, dict): raise serializers.ValidationError("must be a dict") validated = {} for key, val in data.items(): serializer = InputField(data=val) if not serializer.is_valid(): raise serializers.ValidationError({key: serializer.errors}) validated[key] = serializer.validated_data return validated class TriggerValidationMixin: def validate(self, attrs): # trigger_setting 校验 trigger_type = attrs.get('trigger_type') trigger_setting = attrs.get('trigger_setting') if trigger_type and trigger_setting: if trigger_type == TriggerTypeChoices.SCHEDULED: self._validate_scheduled_setting(trigger_setting) elif trigger_type == TriggerTypeChoices.EVENT: self._validate_event_setting(trigger_setting) else: raise AppApiException(500, _('Error trigger type')) return attrs @staticmethod def _validate_required_field(setting, field_name, trigger_type): if field_name not in setting: raise serializers.ValidationError({ 'trigger_setting': _('%s type requires %s field') % (trigger_type, field_name) }) @staticmethod def _validate_non_empty_array(value, field_name): if not isinstance(value, list): raise serializers.ValidationError({ 'trigger_setting': _('%s must be an array') % field_name}) if len(value) == 0: raise serializers.ValidationError({ 'trigger_setting': _('%s must not be empty') % field_name}) @staticmethod def _validate_number_range(values, field_name, min_val, max_val): for val in values: try: num = int(str(val)) if num < min_val or num > max_val: raise ValueError except (ValueError, TypeError): raise serializers.ValidationError({ 'trigger_setting': _('%s values must be between %s and %s') % (field_name, min_val, max_val) }) def _validate_time_array(self, time_list): self._validate_non_empty_array(time_list, 'time') for time_str in time_list: self._validate_time_format(time_str) @staticmethod def _validate_time_format(time_str): import re pattern = r'^([01]\d|2[0-3]):([0-5]\d)$' if not re.match(pattern, str(time_str)): raise serializers.ValidationError({ 'trigger_setting': _('Invalid time format: %s, must be HH:MM (e.g., 09:00)') % time_str }) def _validate_scheduled_setting(self, setting): schedule_type = setting.get('schedule_type') valid_types = ['daily', 'weekly', 'monthly', 'interval','cron'] if schedule_type not in valid_types: raise serializers.ValidationError( {'trigger_setting': _('schedule_type must be one of %s') % ', '.join(valid_types) }) if schedule_type == 'daily': self._validate_daily(setting) elif schedule_type == 'weekly': self._validate_weekly(setting) elif schedule_type == 'monthly': self._validate_monthly(setting) elif schedule_type == 'interval': self._validate_interval(setting) elif schedule_type == 'cron': self._validate_cron(setting) def _validate_daily(self, setting): self._validate_required_field(setting, 'time', 'daily') self._validate_time_array(setting['time']) def _validate_weekly(self, setting): self._validate_required_field(setting, 'days', 'weekly') self._validate_required_field(setting, 'time', 'weekly') days = setting['days'] self._validate_non_empty_array(days, 'days') self._validate_number_range(days, 'days', 1, 7) self._validate_time_array(setting['time']) def _validate_monthly(self, setting): self._validate_required_field(setting, 'days', 'monthly') self._validate_required_field(setting, 'time', 'monthly') days = setting['days'] self._validate_non_empty_array(days, 'days') self._validate_number_range(days, 'days', 1, 31) self._validate_time_array(setting['time']) def _validate_interval(self, setting): self._validate_required_field(setting, 'interval_value', 'interval') self._validate_required_field(setting, 'interval_unit', 'interval') interval_value = setting['interval_value'] interval_unit = setting['interval_unit'] try: value_int = int(interval_value) if value_int < 1: raise ValueError except (ValueError, TypeError): raise serializers.ValidationError({ 'trigger_setting': _('interval_value must be an integer greater than or equal to 1') }) valid_units = ['minutes', 'hours'] if interval_unit not in valid_units: raise serializers.ValidationError({ 'trigger_setting': _('interval_unit must be one of %s') % ', '.join(valid_units) }) @staticmethod def _validate_cron(setting): from apscheduler.triggers.cron import CronTrigger cron_expression: str = setting.get('cron_expression') if not cron_expression: raise serializers.ValidationError({ 'trigger_setting': _('cron type requires cron_expression field') }) try: CronTrigger.from_crontab(cron_expression.strip()) except ValueError: raise serializers.ValidationError({ 'trigger_setting': _('Invalid cron expression: %s') % cron_expression }) @staticmethod def _validate_event_setting(setting): body = setting.get('body') if body is not None and not isinstance(body, list): raise serializers.ValidationError({ 'trigger_setting': _('body must be an array') }) class TriggerTaskCreateRequest(serializers.Serializer): source_type = serializers.ChoiceField(required=True, choices=TriggerTaskTypeChoices) source_id = serializers.CharField(required=True, label=_('source_id')) is_active = serializers.BooleanField(required=False, label=_('Is active')) meta = serializers.DictField(default=dict, required=False) parameter = serializers.DictField(default=dict, required=False) def validate(self, attrs): source_type = attrs.get('source_type') parameter = attrs.get('parameter') if source_type == TriggerTaskTypeChoices.APPLICATION: serializer = ApplicationTaskParameterSerializer(data=parameter) serializer.is_valid(raise_exception=True) attrs['parameter'] = serializer.validated_data if source_type == TriggerTaskTypeChoices.TOOL: serializer = ToolTaskParameterSerializer(data=parameter) serializer.is_valid(raise_exception=True) attrs['parameter'] = serializer.validated_data return attrs class TriggerTaskEditRequest(serializers.Serializer): source_type = serializers.ChoiceField(required=False, choices=TriggerTaskTypeChoices) source_id = serializers.CharField(required=False, label=_('source_id')) is_active = serializers.BooleanField(required=False, label=_('Is active')) meta = serializers.DictField(default=dict, required=False) parameter = serializers.DictField(default=dict, required=False) def validate(self, attrs): source_type = attrs.get('source_type') parameter = attrs.get('parameter') if source_type == TriggerTaskTypeChoices.APPLICATION: serializer = ApplicationTaskParameterSerializer(data=parameter) serializer.is_valid(raise_exception=True) attrs['parameter'] = serializer.validated_data if source_type == TriggerTaskTypeChoices.TOOL: serializer = ToolTaskParameterSerializer(data=parameter) serializer.is_valid(raise_exception=True) attrs['parameter'] = serializer.validated_data return attrs class TriggerEditRequest(TriggerValidationMixin, serializers.Serializer): name = serializers.CharField(required=False, label=_('trigger name')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description')) trigger_type = serializers.ChoiceField(required=False, choices=TriggerTypeChoices) trigger_setting = serializers.DictField(required=False, label=_("trigger setting")) meta = serializers.DictField(default=dict, required=False) trigger_task = TriggerTaskEditRequest(many=True, required=False) class TriggerCreateRequest(TriggerValidationMixin, serializers.Serializer): id = serializers.UUIDField(required=True, label=_("Trigger ID")) name = serializers.CharField(required=True, label=_('trigger name')) desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description')) trigger_type = serializers.ChoiceField(required=True, choices=TriggerTypeChoices) trigger_setting = serializers.DictField(required=True, label=_("trigger setting")) meta = serializers.DictField(default=dict, required=False) is_active = serializers.BooleanField(required=False, label=_('Is active')) trigger_task = TriggerTaskCreateRequest(many=True) class TriggerModelSerializer(serializers.ModelSerializer): class Meta: model = Trigger fields = "__all__" class TriggerTaskModelSerializer(serializers.ModelSerializer): class Meta: model = TriggerTask fields = "__all__" class ApplicationTriggerTaskSerializer(serializers.ModelSerializer): class Meta: model = Application fields = ['id', 'name', 'work_flow', 'icon', 'type'] class ToolTriggerTaskSerializer(serializers.ModelSerializer): class Meta: model = Tool fields = ['id', 'name', 'input_field_list', 'icon'] class TriggerResponse(serializers.ModelSerializer): class Meta: model = Trigger fields = "__all__" class TriggerSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) @transaction.atomic def insert(self, instance, with_valid=True): from trigger.handler.simple_tools import deploy if with_valid: self.is_valid(raise_exception=True) serializer = TriggerCreateRequest(data=instance) serializer.is_valid(raise_exception=True) valid_data = serializer.validated_data trigger_id = valid_data.get('id') if valid_data.get('id') else uuid.uuid7() trigger_model = Trigger( id=trigger_id, name=valid_data.get('name'), workspace_id=self.data.get('workspace_id'), desc=valid_data.get('desc') or '', trigger_type=valid_data.get('trigger_type'), trigger_setting=valid_data.get('trigger_setting'), meta=valid_data.get('meta', {}), is_active=valid_data.get('is_active') or False, user_id=self.data.get('user_id'), ) trigger_model.save() trigger_tasks = valid_data.get('trigger_task') if trigger_tasks: is_active_map = self.batch_get_source_active_status(trigger_tasks) trigger_task_models = [ TriggerTask( id=uuid.uuid7(), trigger_id=trigger_id, source_type=task_data.get('source_type'), source_id=task_data.get('source_id'), is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False, parameter=task_data.get('parameter', {}), meta=task_data.get('meta', {}) ) for task_data in trigger_tasks ] TriggerTask.objects.bulk_create(trigger_task_models) else: raise AppApiException(500, _('Trigger task can not be empty')) if trigger_model.is_active: deploy(TriggerModelSerializer(trigger_model).data, **{}) return TriggerResponse(trigger_model).data @staticmethod def batch_get_source_active_status(trigger_tasks: list) -> Dict[tuple, bool]: """ 批量查询所有 source 的 is_active 状态 返回: {(source_type, source_id): is_active} """ config = { TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'), TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'), } source_ids_by_type = {} for task_data in trigger_tasks: source_type = task_data.get('source_type') source_id = task_data.get('source_id') if source_type not in config: raise AppApiException(500, _('Error source type')) if source_type not in source_ids_by_type: source_ids_by_type[source_type] = [] source_ids_by_type[source_type].append(source_id) is_active_map = {} for source_type, source_ids in source_ids_by_type.items(): source_model, field = config[source_type] source_query_set = QuerySet(source_model).filter(id__in=source_ids).values('id', field) for source in source_query_set: is_active_map[(source_type, str(source['id']))] = source[field] return is_active_map @staticmethod def is_active_source(source_type: str, source_id: str): config = { TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'), TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'), } if source_type not in config: raise AppApiException(500, _('Error source type')) source_model, field = config.get(TriggerTaskTypeChoices(source_type)) source = QuerySet(source_model).filter(id=source_id).first() if not source: raise AppApiException(500, _('%s id does not exist') % source_type) return getattr(source, field) class Batch(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) @transaction.atomic def batch_delete(self, instance: Dict, with_valid=True): from trigger.handler.simple_tools import deploy, undeploy if with_valid: BatchSerializer(data=instance).is_valid(model=Trigger, raise_exception=True) self.is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") trigger_id_list = instance.get("id_list") for trigger_id in trigger_id_list: trigger = QuerySet(Trigger).filter(id=trigger_id).first() undeploy(TriggerModelSerializer(trigger).data, **{}) TaskRecord.objects.filter(trigger_id__in=trigger_id_list).delete() TriggerTask.objects.filter(trigger_id__in=trigger_id_list).delete() Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list).delete() return True @transaction.atomic def batch_switch(self, instance: Dict, with_valid=True): from trigger.handler.simple_tools import deploy, undeploy if with_valid: BatchActiveSerializer(data=instance).is_valid(model=Trigger, raise_exception=True) self.is_valid(raise_exception=True) workspace_id = self.data.get("workspace_id") trigger_id_list = instance.get("id_list") is_active = instance.get("is_active") Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list, is_active=not is_active).update( is_active=is_active) if is_active: for trigger_id in trigger_id_list: trigger = QuerySet(Trigger).filter(id=trigger_id).first() deploy(TriggerModelSerializer(trigger).data, **{}) else: for trigger_id in trigger_id_list: trigger = QuerySet(Trigger).filter(id=trigger_id).first() undeploy(TriggerModelSerializer(trigger).data, **{}) return True class TriggerOperateSerializer(serializers.Serializer): trigger_id = serializers.UUIDField(required=True, label=_('trigger id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) workspace_id = serializers.CharField(required=True, label=_('workspace id')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Trigger id does not exist')) @transaction.atomic def edit(self, instance: Dict, with_valid=True): from trigger.handler.simple_tools import deploy, undeploy if with_valid: self.is_valid() TriggerEditRequest(data=instance).is_valid(raise_exception=True) trigger_id = self.data.get('trigger_id') workspace_id = self.data.get('workspace_id') trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first() if not trigger: raise serializers.ValidationError(_('Trigger not found')) trigger_direct_edit_field_list = ['name', 'desc', 'trigger_type', 'trigger_setting', 'meta', 'is_active'] trigger_deploy_edit_field_list = ['trigger_type', 'trigger_setting', 'is_active'] # is need to redeploy need_redeploy = any(field in instance for field in trigger_deploy_edit_field_list) for field in trigger_direct_edit_field_list: if field in instance: trigger.__setattr__(field, instance.get(field)) trigger.save() # 处理trigger task trigger_tasks = instance.get('trigger_task') if trigger_tasks is not None: # 检查是否为空列表 if not trigger_tasks: raise serializers.ValidationError(_('Trigger must have at least one task')) is_active_map = TriggerSerializer.batch_get_source_active_status(trigger_tasks) trigger_task_model_list = [TriggerTask( id=task_data.get('id') or uuid.uuid7(), trigger_id=trigger_id, source_type=task_data.get('source_type'), source_id=task_data.get('source_id'), is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False, parameter=task_data.get('parameter', []), meta=task_data.get('meta', {}) ) for task_data in trigger_tasks] TriggerTask.objects.filter(trigger_id=trigger_id).delete() TriggerTask.objects.bulk_create(trigger_task_model_list) else: # 用户没提交 trigger_task 字段,确保数据库中有 task if not TriggerTask.objects.filter(trigger_id=trigger_id).exists(): raise serializers.ValidationError(_('Trigger must have at least one task')) # 重新部署触发器任务 if need_redeploy: if trigger.is_active and trigger.trigger_type == 'SCHEDULED': deploy(TriggerModelSerializer(trigger).data, **{}) else: undeploy(TriggerModelSerializer(trigger).data, **{}) return self.one(with_valid=False) def delete(self): from trigger.handler.simple_tools import deploy, undeploy self.is_valid(raise_exception=True) trigger_id = self.data.get('trigger_id') trigger = QuerySet(Trigger).filter(workspace_id=self.data.get('workspace_id'), id=trigger_id).first() if trigger: undeploy(TriggerModelSerializer(trigger).data, **{}) TaskRecord.objects.filter(trigger_id=trigger_id).delete() TriggerTask.objects.filter(trigger_id=trigger_id).delete() Trigger.objects.filter(id=trigger_id).delete() return True def one(self, with_valid=True): if with_valid: self.is_valid() trigger_id = self.data.get('trigger_id') workspace_id = self.data.get('workspace_id') trigger = QuerySet(Trigger).filter(workspace_id=workspace_id, id=trigger_id).first() trigger_tasks = list(QuerySet(TriggerTask).filter(trigger_id=trigger_id)) application_ids = [] tool_ids = [] for task in trigger_tasks: if task.source_type == TriggerTaskTypeChoices.APPLICATION: application_ids.append(task.source_id) elif task.source_type == TriggerTaskTypeChoices.TOOL: tool_ids.append(task.source_id) trigger_task_list = TriggerTaskModelSerializer(trigger_tasks, many=True).data application_task_list = [] if application_ids: applications = Application.objects.filter(workspace_id=workspace_id, id__in=application_ids) application_task_list = ApplicationTriggerTaskSerializer(applications, many=True).data tool_task_list = [] if tool_ids: tools = Tool.objects.filter(workspace_id=workspace_id, id__in=tool_ids) tool_task_list = ToolTriggerTaskSerializer(tools, many=True).data return { **TriggerModelSerializer(trigger).data, 'trigger_task': trigger_task_list, 'application_task_list': application_task_list, 'tool_task_list': tool_task_list, } class TriggerQuerySerializer(serializers.Serializer): name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Trigger name')) type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger type')) is_active = serializers.BooleanField(required=False, allow_null=True, label=_('Is active')) task = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger task')) create_user = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Create user')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) def get_query_set(self): trigger_query_set = QuerySet( model=get_dynamics_model({ 't.name': models.CharField(), 'trigger_type': models.CharField(), 't.workspace_id': models.CharField(), 't.is_active': models.BooleanField(), 't.user_id': models.CharField(), })) task_query_set = QuerySet(model=get_dynamics_model({ 'trigger_task_str': models.CharField(), })) trigger_query_set = trigger_query_set.filter(**{'t.workspace_id': self.data.get("workspace_id")}) if self.data.get("name"): trigger_query_set = trigger_query_set.filter(**{'t.name__icontains': self.data.get("name")}) if self.data.get("type"): trigger_query_set = trigger_query_set.filter(trigger_type=self.data.get("type")) if self.data.get("is_active") is not None: trigger_query_set = trigger_query_set.filter(**{"t.is_active": self.data.get("is_active")}) if self.data.get("task"): task_query_set = task_query_set.filter(trigger_task_str__icontains=self.data.get("task")) if self.data.get("create_user"): trigger_query_set = trigger_query_set.filter(**{"t.user_id": self.data.get("create_user")}) return {"trigger_query_set": trigger_query_set, "task_query_set": task_query_set} def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return native_page_search(current_page, page_size, self.get_query_set(), get_file_content( os.path.join(PROJECT_DIR, "apps", "trigger", "sql", "get_trigger_page_list.sql") )) def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return native_search(self.get_query_set(), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "trigger", "sql", "get_trigger_page_list.sql"))) ================================================ FILE: apps/trigger/serializers/trigger_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: trigger_task.py @date:2026/1/14 16:34 @desc: """ import os from django.db import models from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ChatRecord from common.db.search import native_page_search, get_dynamics_model from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from knowledge.models.knowledge_action import State from maxkb.conf import PROJECT_DIR from tools.models import ToolRecord from trigger.models import TriggerTask, TaskRecord, Trigger class ChatRecordSerializerModel(serializers.ModelSerializer): class Meta: model = ChatRecord fields = ['id', 'chat_id', 'vote_status', 'vote_reason', 'vote_other_content', 'problem_text', 'answer_text', 'message_tokens', 'answer_tokens', 'const', 'improve_paragraph_id_list', 'run_time', 'index', 'answer_text_list', 'details', 'create_time', 'update_time'] class TriggerTaskResponse(serializers.ModelSerializer): class Meta: model = TriggerTask fields = "__all__" class TriggerTaskQuerySerializer(serializers.Serializer): trigger_id = serializers.CharField(required=True, label=_("Trigger ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Trigger id does not exist')) def get_query_set(self): query_set = QuerySet(TriggerTask).filter(workspace_id=self.data.get("workspace_id")).filter( trigger_id=self.data.get("trigger_id")) return query_set def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return [TriggerTaskResponse(row).data for row in self.get_query_set()] class TriggerTaskRecordOperateSerializer(serializers.Serializer): trigger_id = serializers.CharField(required=True, label=_("Trigger ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) trigger_task_id = serializers.CharField(required=True, label=_("Trigger task ID")) trigger_task_record_id = serializers.CharField(required=True, label=_("Trigger task record ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Trigger id does not exist')) def get_execution_details(self, is_valid=True): if is_valid: self.is_valid(raise_exception=True) task_record = QuerySet(TaskRecord).filter(trigger_id=self.data.get("trigger_id"), trigger_task_id=self.data.get("trigger_task_id"), id=self.data.get('trigger_task_record_id')).first() if not task_record: raise AppApiException(500, _('Trigger task record id does not exist')) if task_record.source_type == 'APPLICATION': chat_record = QuerySet(ChatRecord).filter(id=task_record.task_record_id).first() if chat_record: return ChatRecordSerializerModel(chat_record).data return { 'state': 'TRIGGER_ERROR', 'meta': task_record.meta } if task_record.source_type == 'TOOL': tool_record = QuerySet(ToolRecord).filter(id=task_record.task_record_id).first() if tool_record: return { 'id': tool_record.id, 'tool_id': tool_record.tool_id, 'workspace_id': tool_record.workspace_id, 'source_type': tool_record.source_type, 'source_id': tool_record.source_id, 'meta': tool_record.meta, 'state': tool_record.state, 'run_time': tool_record.run_time, 'details': { 'tool_call': { 'index': 1, 'result': tool_record.meta.get('output'), 'params': tool_record.meta.get('input'), 'status': 500 if tool_record.state == State.FAILURE else 200 if tool_record.state == State.SUCCESS else 201, 'type': 'tool-node', 'err_message': tool_record.meta.get('err_message') } } } return { 'state': 'TRIGGER_ERROR', 'meta': task_record.meta } class TriggerTaskRecordQuerySerializer(serializers.Serializer): trigger_id = serializers.CharField(required=True, label=_("Trigger ID")) workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) state = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger state')) name = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger name')) source_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Source type')) order = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Order field')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) workspace_id = self.data.get('workspace_id') query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id')) if workspace_id: query_set = query_set.filter(workspace_id=workspace_id) if not query_set.exists(): raise AppApiException(500, _('Trigger id does not exist')) def get_query_set(self): trigger_query_set = QuerySet( model=get_dynamics_model({ 'ett.create_time': models.DateTimeField(), 'ett.state': models.CharField(), 'sdc.name': models.CharField(), 'ett.workspace_id': models.CharField(), 'ett.trigger_id': models.UUIDField(), 'sdc.source_type': models.CharField() })) trigger_query_set = trigger_query_set.filter( **{'ett.trigger_id': self.data.get("trigger_id")}) if self.data.get("order"): trigger_query_set = trigger_query_set.order_by(self.data.get("order")) else: trigger_query_set = trigger_query_set.order_by("-ett.create_time") if self.data.get('state'): trigger_query_set = trigger_query_set.filter(**{'ett.state': self.data.get('state')}) if self.data.get("name"): trigger_query_set = trigger_query_set.filter(**{'sdc.name__contains': self.data.get('name')}) if self.data.get('source_type'): trigger_query_set = trigger_query_set.filter(**{'sdc.source_type': self.data.get('source_type')}) return trigger_query_set def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return [TriggerTaskResponse(row).data for row in self.get_query_set()] def page(self, current_page, page_size, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return native_page_search(current_page, page_size, self.get_query_set(), get_file_content( os.path.join(PROJECT_DIR, "apps", "trigger", "sql", 'get_trigger_task_record_page_list.sql') )) ================================================ FILE: apps/trigger/sql/get_trigger_page_list.sql ================================================ WITH scheduler AS (SELECT SPLIT_PART(id, ':', 2) as trigger_id, id, next_run_time FROM django_apscheduler_djangojob WHERE id LIKE 'trigger:%%') SELECT * FROM (SELECT t.id, t.workspace_id, t.name, t."desc", t.trigger_type, t.trigger_setting, t.meta::JSON, t.is_active, t.create_time, t.update_time, t.user_id, (SELECT nick_name FROM "user" WHERE id = t.user_id) AS create_user, COALESCE( (ARRAY_AGG(sj.next_run_time ORDER BY sj.next_run_time))[1], NULL ) as next_run_time, COALESCE( JSON_AGG( JSON_BUILD_OBJECT( 'type', tt.source_type, 'name', COALESCE(app.name, tool.name), 'icon', COALESCE(app.icon, tool.icon) ) ), '[]'::JSON ) AS trigger_task, STRING_AGG(COALESCE(app.name, tool.name), ' ') AS trigger_task_str FROM event_trigger t LEFT JOIN scheduler sj ON sj.trigger_id=t.id::text LEFT JOIN event_trigger_task tt ON t.id = tt.trigger_id LEFT JOIN application app ON tt.source_type = 'APPLICATION' AND tt.source_id = app.id LEFT JOIN tool ON tt.source_type = 'TOOL' AND tt.source_id = tool.id ${trigger_query_set} GROUP BY t.id, t.workspace_id, t.name, t.desc, t.trigger_type, t.trigger_setting, t.meta, t.is_active, t.create_time, t.update_time, t.user_id) AS sub ${task_query_set} ORDER BY sub.create_time DESC ================================================ FILE: apps/trigger/sql/get_trigger_task_record_page_list.sql ================================================ WITH source_data_cte AS (SELECT 'APPLICATION' as source_type, id, "name", "desc", "user_id", "workspace_id", "icon", "type", "folder_id" FROM application UNION ALL SELECT 'TOOL' as source_type, id, "name", "desc", "user_id", "workspace_id", "icon", "tool_type"::text as "type", "folder_id" FROM tool) select ett.*, ett.meta::json as meta, sdc.name as source_name, sdc.icon as source_icon, sdc.type as type from event_trigger_task_record ett left join source_data_cte sdc on ett.source_id = sdc.id and ett.source_type = sdc.source_type ================================================ FILE: apps/trigger/tasks.py ================================================ # apps/trigger/tasks.py # coding=utf-8 from __future__ import annotations # 作为 Celery autodiscover 的入口,确保任务模块被导入从而完成注册 from trigger.handler.impl.trigger.scheduled_trigger import deploy_scheduled_trigger # noqa: F401 ================================================ FILE: apps/trigger/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/trigger/urls.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: urls.py @date:2026/1/9 16:15 @desc: """ from django.urls import path from . import views from .handler.impl.trigger.event_trigger import EventTriggerView app_name = "trigger" # @formatter:off urlpatterns = [ path('workspace//trigger', views.TriggerView.as_view(), name='trigger'), path('workspace//trigger/batch_delete', views.TriggerView.BatchDelete.as_view(), name='delete batch'), path('workspace//trigger/batch_activate', views.TriggerView.BatchActivate.as_view(), name='activate batch'), path('workspace//trigger/', views.TriggerView.Operate.as_view(), name='trigger operate'), path('workspace//trigger//', views.TriggerView.Page.as_view(), name='trigger_page'), path('workspace////trigger/', views.TaskSourceTriggerView.Operate.as_view(), name='task source trigger operate'), path('workspace////trigger', views.TaskSourceTriggerView.as_view(), name='task source trigger'), path('workspace//trigger//task_record//', views.TriggerTaskRecordPageView.as_view(), name='trigger_task_record'), path('workspace//trigger//task', views.TriggerTaskView.as_view(), name='task'), path('trigger/v1/webhook/', EventTriggerView.as_view(), name='trigger_webhook'), path('workspace//trigger//trigger_task//trigger_task_record/', views.TriggerTaskRecordExecutionDetailsView.as_view(), name='task source trigger'), ] ================================================ FILE: apps/trigger/views/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: __init__.py.py @date:2026/1/9 16:15 @desc: """ from .trigger import * from .trigger_task import * ================================================ FILE: apps/trigger/views/trigger.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:niu @file: trigger.py @date:2026/1/14 11:44 @desc: """ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from application.api.application_api import ApplicationCreateAPI from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants, \ Permission, Group, Operate from common.log.log import log from common.result import DefaultResultSerializer from trigger.models import Trigger from trigger.serializers.task_source_trigger import TaskSourceTriggerListSerializer, TaskSourceTriggerOperateSerializer, \ TaskSourceTriggerSerializer from trigger.serializers.trigger import TriggerQuerySerializer, TriggerOperateSerializer from trigger.api.trigger import TriggerCreateAPI, TriggerOperateAPI, TriggerEditAPI, TriggerBatchDeleteAPI, \ TriggerBatchActiveAPI, TaskSourceTriggerOperateAPI, TaskSourceTriggerAPI, TaskSourceTriggerCreateAPI, \ TriggerQueryAPI, TriggerQueryPageAPI from trigger.serializers.trigger import TriggerSerializer def get_trigger_operation_object(trigger_id): trigger_model = QuerySet(model=Trigger).filter(id=trigger_id).first() if trigger_model is not None: return { "name": trigger_model.name } def get_trigger_operation_object_batch(trigger_id_list): trigger_model_list = QuerySet(model=Trigger).filter(id__in=trigger_id_list) if trigger_model_list is not None: return { "name": f'[{",".join([trigger_model.name for trigger_model in trigger_model_list])}]', "trigger_list": [{'name': trigger_model.name, 'type': trigger_model.type} for trigger_model in trigger_model_list] } class TriggerView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create trigger'), summary=_('Create trigger'), operation_id=_('Create trigger'), # type: ignore parameters=TriggerCreateAPI.get_parameters(), request=TriggerCreateAPI.get_request(), responses=TriggerCreateAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_CREATE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Create trigger", get_operation_object=lambda r, k: r.data.get('name'), ) def post(self, request: Request, workspace_id: str): return result.success(TriggerSerializer( data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data)) @extend_schema( methods=['GET'], description=_('Get the trigger list'), summary=_('Get the trigger list'), operation_id=_('Get the trigger list'), # type: ignore parameters=TriggerQueryAPI.get_parameters(), responses=TriggerQueryAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) def get(self, request: Request, workspace_id: str): return result.success(TriggerQuerySerializer(data={ 'workspace_id': workspace_id, 'name': request.query_params.get('name'), 'type': request.query_params.get('type'), 'task': request.query_params.get('task'), 'is_active': request.query_params.get('is_active'), 'create_user': request.query_params.get('create_user'), }).list()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get trigger details'), summary=_('Get trigger details'), operation_id=_('Get trigger details'), # type: ignore parameters=TriggerOperateAPI.get_parameters(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Get trigger details", get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')), ) def get(self, request: Request, workspace_id: str, trigger_id: str): return result.success(TriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id} ).one()) @extend_schema( methods=['PUT'], description=_('Modify the trigger'), summary=_('Modify the trigger'), operation_id=_('Modify the trigger'), # type: ignore parameters=TriggerOperateAPI.get_parameters(), request=TriggerEditAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Modify the trigger", get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')), ) def put(self, request: Request, workspace_id: str, trigger_id: str): return result.success(TriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id} ).edit(request.data)) @extend_schema( methods=['DELETE'], description=_('Delete the trigger'), summary=_('Delete the trigger'), operation_id=_('Delete the trigger'), # type: ignore parameters=TriggerOperateAPI.get_parameters(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Delete the trigger", get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')), ) def delete(self, request: Request, workspace_id: str, trigger_id: str): return result.success(TriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id} ).delete()) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Delete trigger in batches'), summary=_('Delete trigger in batches'), operation_id=_('Delete trigger in batches'), # type: ignore parameters=TriggerBatchDeleteAPI.get_parameters(), request=TriggerBatchDeleteAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_DELETE.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Delete trigger in batches", get_operation_object=lambda r, k: get_trigger_operation_object_batch(r.data.get('id_list')), ) def put(self, request: Request, workspace_id: str): return result.success(TriggerSerializer.Batch( data={'workspace_id': workspace_id, 'user_id': request.user.id} ).batch_delete(request.data)) class BatchActivate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['PUT'], description=_('Activate trigger in batches'), summary=_('Activate trigger in batches'), operation_id=_('Activate trigger in batches'), # type: ignore parameters=TriggerBatchDeleteAPI.get_parameters(), request=TriggerBatchActiveAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_EDIT.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) @log( menu="Trigger", operate="Activate trigger in batches", get_operation_object=lambda r, k: get_trigger_operation_object_batch(r.data.get('id_list')), ) def put(self, request: Request, workspace_id: str): return result.success(TriggerSerializer.Batch( data={'workspace_id': workspace_id, 'user_id': request.user.id} ).batch_switch(request.data)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get the trigger list by page'), summary=_('Get the trigger list by page'), operation_id=_('Get the trigger list by page'), # type: ignore parameters=TriggerQueryPageAPI.get_parameters(), responses=TriggerQueryPageAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) def get(self, request: Request, workspace_id: str, current_page: int, page_size: int): return result.success(TriggerQuerySerializer(data={ 'workspace_id': workspace_id, 'name': request.query_params.get('name'), 'task': request.query_params.get('task'), 'type': request.query_params.get('type'), 'is_active': request.query_params.get('is_active'), 'create_user': request.query_params.get('create_user'), }).page(current_page, page_size)) class TaskSourceTriggerView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], description=_('Create trigger in source'), summary=_('Create trigger in source'), operation_id=_('Create trigger in source'), # type: ignore parameters=TaskSourceTriggerCreateAPI.get_parameters(), request=TaskSourceTriggerCreateAPI.get_request(), responses=TaskSourceTriggerCreateAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_CREATE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_CREATE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}" ), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log( menu="Trigger", operate="Create trigger in source", get_operation_object=lambda r, k: r.data.get('name'), ) def post(self, request: Request, workspace_id: str, source_type: str, source_id: str): return result.success(TaskSourceTriggerSerializer(data={ 'workspace_id': workspace_id, 'user_id': request.user.id }).insert({**request.data, 'source_id': source_id, 'workspace_id': workspace_id, 'is_active': True, 'source_type': source_type})) @extend_schema( methods=['GET'], description=_('Get the trigger list of source'), summary=_('Get the trigger list of source'), operation_id=_('Get the trigger list of source'), # type: ignore parameters=TaskSourceTriggerAPI.get_parameters(), responses=DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}" ), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, source_type: str, source_id: str): return result.success(TaskSourceTriggerListSerializer(data={ 'workspace_id': workspace_id, 'source_id': source_id, 'source_type': source_type, }).list()) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get Task source trigger details'), summary=_('Get Task source trigger details'), operation_id=_('Get Task source trigger details'), # type: ignore parameters=TaskSourceTriggerOperateAPI.get_parameters(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_READ, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}" ), RoleConstants.USER.get_workspace_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str): return result.success(TaskSourceTriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'source_id': source_id, 'source_type': source_type} ).one()) @extend_schema( methods=['PUT'], description=_('Modify the task source trigger'), summary=_('Modify the task source trigger'), operation_id=_('Modify the task source trigger'), # type: ignore parameters=TaskSourceTriggerOperateAPI.get_parameters(), request=TaskSourceTriggerOperateAPI.get_request(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_EDIT, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}" ), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log( menu="Trigger", operate="Modify the source point trigger", get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')), ) def put(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str): return result.success(TaskSourceTriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'source_id': source_id, 'source_type': source_type} ).edit(request.data)) @extend_schema( methods=['DELETE'], description=_('Delete the task source trigger'), summary=_('Delete the task source trigger'), operation_id=_('Delete the task source trigger'), # type: ignore parameters=TaskSourceTriggerOperateAPI.get_parameters(), responses=result.DefaultResultSerializer, tags=[_('Trigger')] # type: ignore ) @has_permissions( lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_DELETE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE" ), lambda r, kwargs: Permission(group=Group(kwargs.get("source_type")), operate=Operate.TRIGGER_DELETE, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}" ), ViewPermission([RoleConstants.USER.get_workspace_role()], [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')), operate=Operate.SELF, resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}")], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log( menu="Trigger", operate="Delete the source point trigger", get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')), ) def delete(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str): return result.success(TaskSourceTriggerOperateSerializer( data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'source_id': source_id, 'source_type': source_type} ).delete()) ================================================ FILE: apps/trigger/views/trigger_task.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: trigger_task.py @date:2026/1/14 16:01 @desc: """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth import TokenAuth from common.auth.authentication import has_permissions from common import result from trigger.api.trigger_task import TriggerTaskRecordExecutionDetailsAPI, TriggerTaskRecordPageAPI, TriggerTaskAPI from trigger.serializers.trigger_task import TriggerTaskQuerySerializer, TriggerTaskRecordQuerySerializer, \ TriggerTaskRecordOperateSerializer from common.constants.permission_constants import PermissionConstants, RoleConstants class TriggerTaskView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get the task list of triggers'), summary=_('Get the task list of triggers'), operation_id=_('Get the task list of triggers'), # type: ignore parameters=TriggerTaskAPI.get_parameters(), responses=TriggerTaskAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) def get(self, request: Request, workspace_id: str, trigger_id: str): return result.success( TriggerTaskQuerySerializer(data={'workspace_id': workspace_id, 'trigger_id': trigger_id}).list()) class TriggerTaskRecordView(APIView): pass class TriggerTaskRecordExecutionDetailsView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Retrieve detailed records of tasks executed by the trigger.'), summary=_('Retrieve detailed records of tasks executed by the trigger.'), operation_id=_('Retrieve detailed records of tasks executed by the trigger.'), # type: ignore parameters=TriggerTaskRecordExecutionDetailsAPI.get_parameters(), responses=TriggerTaskRecordExecutionDetailsAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) def get(self, request: Request, workspace_id: str, trigger_id: str, trigger_task_id: str, trigger_task_record_id: str): return result.success( TriggerTaskRecordOperateSerializer( data={'workspace_id': workspace_id, 'trigger_id': trigger_id, 'trigger_task_id': trigger_task_id, 'trigger_task_record_id': trigger_task_record_id}) .get_execution_details()) class TriggerTaskRecordPageView(APIView): authentication_classes = [TokenAuth] @extend_schema( methods=['GET'], description=_('Get a paginated list of execution records for trigger tasks.'), summary=_('Get a paginated list of execution records for trigger tasks.'), operation_id=_('Get a paginated list of execution records for trigger tasks.'), # type: ignore parameters=TriggerTaskRecordPageAPI.get_parameters(), responses=TriggerTaskRecordPageAPI.get_response(), tags=[_('Trigger')] # type: ignore ) @has_permissions( PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(), RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) def get(self, request: Request, workspace_id: str, trigger_id: str, current_page: int, page_size: int): return result.success( TriggerTaskRecordQuerySerializer( data={'workspace_id': workspace_id, 'trigger_id': trigger_id, 'source_type': request.query_params.get('source_type'), 'state': request.query_params.get('state'), 'name': request.query_params.get('name')}) .page(current_page, page_size)) ================================================ FILE: apps/users/__init__.py ================================================ ================================================ FILE: apps/users/admin.py ================================================ from django.contrib import admin # Register your models here. ================================================ FILE: apps/users/api/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/14 10:28 @desc: """ from .login import * ================================================ FILE: apps/users/api/login.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: login.py @date:2025/4/14 10:30 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer from users.serializers.login import LoginResponse, LoginRequest, CaptchaResponse class ApiLoginResponse(ResultSerializer): def get_data(self): return LoginResponse() """ Request 和Response 都可以使用此方法 使用serializers.Serializer class LoginRequest(serializers.Serializer): username = serializers.CharField(required=True, max_length=64, help_text=_("Username"), label=_("Username")) password = serializers.CharField(required=True, max_length=128, label=_("Password")) 使用serializers.ModelSerializer Request不要使用serializers.ModelSerializer的方式 class LoginRequest(serializers.ModelSerializer): class Meta: model = User fields = ['username', 'password'] """ class LoginAPI(APIMixin): @staticmethod def get_request(): return LoginRequest @staticmethod def get_response(): return ApiLoginResponse @staticmethod def get_parameters(): return [OpenApiParameter( name="code", type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] class ApiCaptchaResponse(ResultSerializer): def get_data(self): return CaptchaResponse() class CaptchaAPI(APIMixin): @staticmethod def get_response(): return ApiCaptchaResponse ================================================ FILE: apps/users/api/user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 19:23 @desc: """ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, DefaultResultSerializer from users.serializers.user import UserProfileResponse, CreateUserSerializer, UserManageSerializer, \ UserInstanceSerializer, RePasswordSerializer, CheckCodeSerializer, SendEmailSerializer from django.utils.translation import gettext_lazy as _ from rest_framework import serializers class ApiUserProfileResponse(ResultSerializer): def get_data(self): return UserProfileResponse() class RoleSettingRequestSerializer(serializers.Serializer): role_id = serializers.CharField(required=True, label=_('Role ID')) workspace_ids = serializers.ListField( child=serializers.CharField(required=False), required=False, label=_('Workspace IDs') ) class CreateUserRequestSerializer(CreateUserSerializer): role_setting = RoleSettingRequestSerializer(required=False, label=_('Role Setting'), allow_null=True, many=True) class UserProfileAPI(APIMixin): @staticmethod def get_response(): return ApiUserProfileResponse @staticmethod def get_request(): return CreateUserRequestSerializer @staticmethod def get_parameters(): return [OpenApiParameter( name="user_id", description=_('User ID'), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] class WorkspaceUserAPI(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description=_('Workspace ID'), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] @staticmethod def get_response(): return WorkspaceUserListResponse class WorkspaceUser(serializers.Serializer): id = serializers.CharField(required=True, label=_('id')) username = serializers.CharField(required=True, label=_('Username')) class WorkspaceUserListResponse(ResultSerializer): def get_data(self): return WorkspaceUser(many=True) class UserPasswordResponse(APIMixin): @staticmethod def get_response(): return PasswordResponse class Password(serializers.Serializer): password = serializers.CharField(required=True, label=_('Password')) class PasswordResponse(ResultSerializer): def get_data(self): return Password() class EditUserRequestSerializer(UserManageSerializer.UserEditInstance): role_setting = RoleSettingRequestSerializer(required=False, label=_('Role Setting'), allow_null=True, many=True) class EditUserApi(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="user_id", description=_('User ID'), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] @staticmethod def get_request(): return EditUserRequestSerializer class DeleteUserApi(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="user_id", description=_('User ID'), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=True, )] @staticmethod def get_request(): return serializers.ListSerializer(child=serializers.CharField(required=True), required=True, label=_('User IDs')) class ChangeUserPasswordApi(APIMixin): @staticmethod def get_request(): return UserManageSerializer.RePasswordInstance class UserListResponse(ResultSerializer): def get_data(self): return UserInstanceSerializer(many=True) class UserPageApi(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="email_or_username", description=_('Email or Username'), type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, )] @staticmethod def get_response(): return UserListResponse class UserListApi(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( name="workspace_id", description=_('Workspace ID'), type=OpenApiTypes.STR, location=OpenApiParameter.PATH, required=False, )] @staticmethod def get_response(): return UserListResponse class TestWorkspacePermissionUserApi(APIMixin): @staticmethod def get_parameters(): return [OpenApiParameter( # 参数的名称是done name="workspace_id", # 对参数的备注 description="工作空间id", # 指定参数的类型 type=OpenApiTypes.STR, location=OpenApiParameter.PATH, # 指定必须给 required=True, )] class ResetPasswordAPI(APIMixin): @staticmethod def get_request(): return RePasswordSerializer class CheckCodeAPI(APIMixin): @staticmethod def get_request(): return CheckCodeSerializer @staticmethod def get_response(): return DefaultResultSerializer class SendEmailAPI(APIMixin): @staticmethod def get_request(): return SendEmailSerializer @staticmethod def get_response(): return DefaultResultSerializer class LanguageSerializer(serializers.Serializer): language = serializers.CharField(required=True, label=_('Language')) class SwitchUserLanguageAPI(APIMixin): @staticmethod def get_request(): return LanguageSerializer ================================================ FILE: apps/users/apps.py ================================================ from django.apps import AppConfig class UsersConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'users' def ready(self): from ops.celery import signal_handler # noqa ================================================ FILE: apps/users/migrations/0001_initial.py ================================================ # Generated by Django 5.2.4 on 2025-07-14 03:50 import uuid_utils.compat from django.db import migrations, models from common.constants.permission_constants import RoleConstants from common.utils.common import password_encrypt from maxkb.const import CONFIG default_password = CONFIG.get('DEFAULT_PASSWORD', 'MaxKB@123..') def insert_default_data(apps, schema_editor): UserModel = apps.get_model('users', 'User') UserModel.objects.create(id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', email='', username='admin', nick_name="系统管理员", password=password_encrypt(default_password), role=RoleConstants.ADMIN.name, is_active=True) class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='User', fields=[ ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), ('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='邮箱')), ('phone', models.CharField(db_index=True, default='', max_length=20, verbose_name='电话')), ('nick_name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='昵称')), ('username', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='用户名')), ('password', models.CharField(max_length=150, verbose_name='密码')), ('role', models.CharField(max_length=150, verbose_name='角色')), ('source', models.CharField(db_index=True, default='LOCAL', max_length=10, verbose_name='来源')), ('is_active', models.BooleanField(db_index=True, default=True)), ('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')), ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, null=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, db_index=True, null=True, verbose_name='修改时间')), ], options={ 'db_table': 'user', }, ), migrations.RunPython(insert_default_data) ] ================================================ FILE: apps/users/migrations/__init__.py ================================================ ================================================ FILE: apps/users/models/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/14 10:20 @desc: """ from .user import * ================================================ FILE: apps/users/models/user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 10:20 @desc: """ import uuid_utils.compat as uuid from django.db import models from common.utils.common import password_encrypt class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") source = models.CharField(max_length=10, verbose_name="来源", default="LOCAL", db_index=True) is_active = models.BooleanField(default=True, db_index=True) language = models.CharField(max_length=10, verbose_name="语言", null=True, default=None) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True, db_index=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True, db_index=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [] class Meta: db_table = "user" def set_password(self, row_password): self.password = password_encrypt(row_password) self._password = row_password ================================================ FILE: apps/users/serializers/__init__.py ================================================ ================================================ FILE: apps/users/serializers/login.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: login.py @date:2025/4/14 11:08 @desc: """ import base64 import datetime import json from captcha.image import ImageCaptcha from django.core import signing from django.core.cache import cache from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ApplicationAccessToken from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException from common.utils.common import password_encrypt, get_random_chars from common.utils.rsa_util import encrypt, decrypt from maxkb.const import CONFIG from users.models import User class LoginRequest(serializers.Serializer): username = serializers.CharField(required=True, max_length=64, help_text=_("Username"), label=_("Username")) password = serializers.CharField(required=True, max_length=128, label=_("Password")) captcha = serializers.CharField(required=False, max_length=64, label=_('captcha'), allow_null=True, allow_blank=True) encryptedData = serializers.CharField(required=False, label=_('encryptedData'), allow_null=True, allow_blank=True) system_version, system_get_key = Cache_Version.SYSTEM.value class LoginResponse(serializers.Serializer): """ 登录响应对象 """ token = serializers.CharField(required=True, label=_("token")) def record_login_fail(username: str, expire: int = 600): """记录登录失败次数""" if not username: return fail_key = system_get_key(f'system_{username}') fail_count = cache.get(fail_key, version=system_version) if fail_count is None: cache.set(fail_key, 1, timeout=expire, version=system_version) else: cache.incr(fail_key, 1, version=system_version) def record_login_fail_lock(username: str, expire: int = 10): if not username: return fail_key = system_get_key(f'system_{username}_lock_count') fail_count = cache.get(fail_key, version=system_version) if fail_count is None: cache.set(fail_key, 1, timeout=expire * 60, version=system_version) else: cache.incr(fail_key, 1, version=system_version) class LoginSerializer(serializers.Serializer): @staticmethod def get_auth_setting(): """获取认证设置""" auth_setting_model = DatabaseModelManage.get_model('auth_setting') auth_setting = {} if auth_setting_model: setting_obj = auth_setting_model.objects.filter(param_key='auth_setting').first() if setting_obj: try: auth_setting = json.loads(setting_obj.param_value) or {} except Exception: auth_setting = {} return auth_setting @staticmethod def login(instance): # 解密数据 username = instance.get("username", "") encrypted_data = instance.get("encryptedData", "") if encrypted_data: decrypted_data = json.loads(decrypt(encrypted_data)) instance.update(decrypted_data) try: LoginRequest(data=instance).is_valid(raise_exception=True) except serializers.ValidationError: raise except Exception as e: raise AppApiException(500, str(e)) password = instance.get("password") captcha = instance.get("captcha", "") # 获取认证配置 auth_setting = LoginSerializer.get_auth_setting() max_attempts = auth_setting.get("max_attempts", 1) failed_attempts = auth_setting.get("failed_attempts", 5) lock_time = auth_setting.get("lock_time", 10) # 检查许可证有效性 license_validator = DatabaseModelManage.get_model('license_is_valid') or (lambda: False) is_license_valid = license_validator() if license_validator() is not None else False if is_license_valid: # 检查账户是否被锁定 if LoginSerializer._is_account_locked(username, failed_attempts): raise AppApiException( 1005, _("This account has been locked for %s minutes, please try again later") % lock_time ) # 验证验证码 if LoginSerializer._need_captcha(username, max_attempts): LoginSerializer._validate_captcha(username, captcha) # 验证用户凭据 user = QuerySet(User).filter( username=username, password=password_encrypt(password) ).first() if not user: LoginSerializer._handle_failed_login(username, is_license_valid, failed_attempts, lock_time) raise AppApiException(500, _('The username or password is incorrect')) if not user.is_active: raise AppApiException(1005, _("The user has been disabled, please contact the administrator!")) # 清除失败计数并生成令牌 cache.delete(system_get_key(f'system_{username}'), version=system_version) cache.delete(system_get_key(f'system_{username}_lock'), version=system_version) token = signing.dumps({ 'username': user.username, 'id': str(user.id), 'email': user.email, 'type': AuthenticationType.SYSTEM_USER.value }) version, get_key = Cache_Version.TOKEN.value timeout = CONFIG.get_session_timeout() cache.set(get_key(token), user, timeout=timeout, version=version) return {'token': token} @staticmethod def _is_account_locked(username: str, failed_attempts: int) -> bool: """检查账户是否被锁定""" if failed_attempts == -1: return False lock_cache = cache.get(system_get_key(f'system_{username}_lock'), version=system_version) return bool(lock_cache) @staticmethod def _need_captcha(username: str, max_attempts: int) -> bool: """判断是否需要验证码""" if max_attempts == -1: return False elif max_attempts > 0: fail_count = cache.get(system_get_key(f'system_{username}'), version=system_version) or 0 return fail_count >= max_attempts return True @staticmethod def _validate_captcha(username: str, captcha: str) -> None: """验证验证码""" if not captcha: raise AppApiException(1005, _("Captcha is required")) captcha_cache = cache.get( Cache_Version.CAPTCHA.get_key(captcha=f"system_{username}"), version=Cache_Version.CAPTCHA.get_version() ) if captcha_cache is None or captcha.lower() != captcha_cache: raise AppApiException(1005, _("Captcha code error or expiration")) @staticmethod def _handle_failed_login(username: str, is_license_valid: bool, failed_attempts: int, lock_time: int) -> None: """处理登录失败""" record_login_fail(username) record_login_fail_lock(username, lock_time) if not is_license_valid or failed_attempts <= 0: return fail_count = cache.get(system_get_key(f'system_{username}_lock_count'), version=system_version) or 0 remain_attempts = failed_attempts - fail_count if remain_attempts > 0: raise AppApiException( 1005, _("Login failed %s times, account will be locked, you have %s more chances !") % ( failed_attempts, remain_attempts ) ) elif remain_attempts == 0: cache.set( system_get_key(f'system_{username}_lock'), 1, timeout=lock_time * 60, version=system_version ) raise AppApiException( 1005, _("This account has been locked for %s minutes, please try again later") % lock_time ) class CaptchaResponse(serializers.Serializer): """ 登录响应对象 """ captcha = serializers.CharField(required=True, label=_("captcha")) class CaptchaSerializer(serializers.Serializer): @staticmethod def generate(username: str, type: str = 'system'): auth_setting = LoginSerializer.get_auth_setting() max_attempts = auth_setting.get("max_attempts", 1) need_captcha = True if max_attempts == -1: need_captcha = False elif max_attempts > 0: fail_count = cache.get(system_get_key(f'system_{username}'), version=system_version) or 0 need_captcha = fail_count >= max_attempts return CaptchaSerializer._generate_captcha_if_needed(username, type, need_captcha) @staticmethod def chat_generate(username: str, type: str = 'chat', access_token: str = ''): application_access_token = ApplicationAccessToken.objects.filter( access_token=access_token ).first() if not application_access_token: raise AppApiException(1005, _('Invalid access token')) auth_setting = application_access_token.authentication_value max_attempts = auth_setting.get("max_attempts", 1) need_captcha = True if max_attempts == -1: need_captcha = False elif max_attempts > 0: fail_count = cache.get(system_get_key(f'{type}_{username}'), version=system_version) or 0 need_captcha = fail_count >= max_attempts return CaptchaSerializer._generate_captcha_if_needed(username, type, need_captcha) @staticmethod def _generate_captcha_if_needed(username: str, type: str, need_captcha: bool): """ 提取的公共验证码生成方法 """ if need_captcha: chars = get_random_chars() image = ImageCaptcha() data = image.generate(chars) captcha = base64.b64encode(data.getbuffer()) cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(), timeout=300, version=Cache_Version.CAPTCHA.get_version()) return {'captcha': 'data:image/png;base64,' + captcha.decode()} return {'captcha': ''} ================================================ FILE: apps/users/serializers/user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 19:18 @desc: """ import datetime import os import random import re from collections import defaultdict from django.core.cache import cache from django.core.mail.backends.smtp import EmailBackend from django.db import transaction from django.db.models import Q, QuerySet from rest_framework import serializers import uuid_utils.compat as uuid from common.constants.cache_version import Cache_Version from common.constants.exception_code_constants import ExceptionCodeConstants from common.constants.permission_constants import RoleConstants, Auth, ResourceAuthType, ResourcePermissionRole, \ ResourcePermission from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import page_search from common.exception.app_exception import AppApiException from common.utils.common import valid_license, password_encrypt, get_random_chars from common.utils.rsa_util import decrypt from maxkb import settings from maxkb.conf import PROJECT_DIR from system_manage.models import SystemSetting, SettingType, AuthTargetType, WorkspaceUserResourcePermission from users.models import User from django.utils.translation import gettext_lazy as _, to_locale from django.core import validators from django.core.mail import send_mail from django.utils.translation import get_language PASSWORD_REGEX = re.compile( r"^" # 开始 r"(?=.*[a-z])" # 至少一个小写字母 r"(?=.*[-_!@#$%^&*`~.()+=])" # 至少一个指定的特殊字符 r"(?:(?=.*[A-Z])|(?=.*\d))" # 至少一个大写字母 或 数字 r"[a-zA-Z0-9-_!@#$%^&*`~.()+=]{6,20}" # 总长度6~20个合法字符 r"$" # 结束 ) version, get_key = Cache_Version.SYSTEM.value class UserProfileResponse(serializers.ModelSerializer): is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password')) permissions = serializers.ListField(required=True, label=_('permissions')) class Meta: model = User fields = ['id', 'username', 'nick_name', 'email', 'role', 'permissions', 'language', 'is_edit_password'] class CreateUserSerializer(serializers.Serializer): username = serializers.CharField(required=True, label=_('Username')) password = serializers.CharField(required=True, label=_('Password')) email = serializers.EmailField(required=True, label=_('Email')) nick_name = serializers.CharField(required=False, label=_('Nick name')) phone = serializers.CharField(required=False, label=_('Phone')) source = serializers.CharField(required=False, label=_('Source'), default='LOCAL') defaultPermission = serializers.CharField(required=False, label=_('defaultPermission')) def is_workspace_manage(user_id: str, workspace_id: str): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None if is_x_pack_ee: return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter( workspace_id=workspace_id, user_id=user_id, role__type=RoleConstants.WORKSPACE_MANAGE.value.__str__()).exists() return QuerySet(User).filter(id=user_id, role=RoleConstants.ADMIN.value.__str__()).exists() def get_workspace_list_by_user(user_id): get_workspace_list = DatabaseModelManage.get_model('get_workspace_list_by_user') license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False) if get_workspace_list is not None and license_is_valid(): return get_workspace_list(user_id) return [{'id': 'default', 'name': 'default'}] class UserProfileSerializer(serializers.Serializer): @staticmethod def profile(user: User, auth: Auth): """ 获取用户详情 @param user: 用户对象 @param auth: 认证对象 @return: """ workspace_list = get_workspace_list_by_user(user.id) user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping") role_name = [user.role] if user_role_relation_model: user_role_relations = ( user_role_relation_model.objects .filter(user_id=user.id) .select_related('role') .distinct('role_id') ) role_name = [relation.role.role_name for relation in user_role_relations] return { 'id': user.id, 'username': user.username, 'nick_name': user.nick_name, 'email': user.email, 'source': user.source, 'role': auth.role_list, 'permissions': auth.permission_list, 'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.source == 'LOCAL' else False, 'language': user.language, 'workspace_list': workspace_list, 'role_name': role_name } class UserInstanceSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email', 'phone', 'is_active', 'role', 'nick_name', 'create_time', 'update_time', 'source'] class UserManageSerializer(serializers.Serializer): class UserInstance(serializers.Serializer): email = serializers.EmailField( required=True, label=_("Email"), validators=[validators.EmailValidator( message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code )] ) username = serializers.CharField( required=True, label=_("Username"), max_length=64, min_length=4, validators=[ validators.RegexValidator( regex=re.compile("^.{4,64}$"), message=_('Username must be 4-64 characters long') ) ] ) password = serializers.CharField( required=True, label=_("Password"), max_length=20, min_length=6, validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) nick_name = serializers.CharField( required=True, label=_("Nick name"), max_length=64, ) phone = serializers.CharField( required=False, label=_("Phone"), max_length=20, allow_null=True, allow_blank=True ) source = serializers.CharField( required=False, label=_("Source"), max_length=20, default="LOCAL" ) def is_valid(self, *, raise_exception=True): super().is_valid(raise_exception=True) self._check_unique_username_and_email() def _check_unique_username_and_email(self): username = self.data.get('username') email = self.data.get('email') nick_name = self.data.get('nick_name') user = User.objects.filter(Q(username=username) | Q(email=email) | Q(nick_name=nick_name)).first() if user: if user.email == email: raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception() if user.username == username: raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception() if user.nick_name == nick_name: raise ExceptionCodeConstants.NICKNAME_IS_EXIST.value.to_app_api_exception() class Query(serializers.Serializer): username = serializers.CharField( required=False, label=_("Username"), max_length=64, allow_blank=True ) nick_name = serializers.CharField( required=False, label=_("Nick Name"), max_length=64, allow_blank=True ) email = serializers.CharField( required=False, label=_("Email"), allow_blank=True, ) is_active = serializers.BooleanField( required=False, label=_("Is active"), ) source = serializers.CharField( required=False, label=_("Source"), allow_blank=True, ) def get_query_set(self): username = self.data.get('username') nick_name = self.data.get('nick_name') email = self.data.get('email') is_active = self.data.get('is_active', None) source = self.data.get('source', None) query_set = QuerySet(User) if username is not None: query_set = query_set.filter(username__contains=username) if nick_name is not None: query_set = query_set.filter(nick_name__contains=nick_name) if email is not None: query_set = query_set.filter(email__contains=email) if is_active is not None: query_set = query_set.filter(is_active=is_active) if source is not None: query_set = query_set.filter(source=source) query_set = query_set.order_by("-create_time") return query_set def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) return [{'id': user_model.id, 'username': user_model.username, 'email': user_model.email} for user_model in self.get_query_set()] def page(self, current_page: int, page_size: int, user_id: str, with_valid=True): if with_valid: self.is_valid(raise_exception=True) result = page_search(current_page, page_size, self.get_query_set(), post_records_handler=lambda u: UserInstanceSerializer(u).data) role_model = DatabaseModelManage.get_model("role_model") user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping") def _get_user_roles(user_ids, is_admin=True): workspace_model = DatabaseModelManage.get_model("workspace_model") if not (role_model and user_role_relation_model and workspace_model): return {} workspace_mapping = {str(workspace_model.id): workspace_model.name for workspace_model in workspace_model.objects.all()} # 获取所有相关角色关系,并预加载角色信息 user_role_relations = ( user_role_relation_model.objects .filter(user_id__in=user_ids) .select_related('role') .distinct('user_id', 'role_id', 'workspace_id') # 确保组合唯一性 ) # 构建用户ID到角色名称列表的映射 user_role_mapping = defaultdict(set) # 使用 set 去重 # 构建用户ID到角色ID与工作空间ID映射 user_role_setting_mapping = defaultdict(lambda: defaultdict(list)) user_role_workspace_mapping = defaultdict(lambda: defaultdict(list)) for relation in user_role_relations: user_id = str(relation.user_id) role_id = relation.role_id workspace_id = relation.workspace_id if not is_admin and relation.role.type == RoleConstants.ADMIN.name: continue user_role_mapping[user_id].add(relation.role.role_name) user_role_setting_mapping[user_id][role_id].append(workspace_id) user_role_workspace_mapping[user_id][relation.role.role_name].append( workspace_mapping.get(workspace_id, workspace_id)) # 将 set 转换为 list 以符合返回格式 user_role_mapping = {uid: list(roles) for uid, roles in user_role_mapping.items()} # 转换为所需的结构 result_user_role_setting_mapping = { user_id: [{"role_id": role_id, "workspace_ids": workspace_ids} for role_id, workspace_ids in roles.items()] for user_id, roles in user_role_setting_mapping.items() } result_user_role_workspace_mapping = { user_id: {role_name: workspace_names for role_name, workspace_names in roles.items()} for user_id, roles in user_role_workspace_mapping.items() } return user_role_mapping, result_user_role_setting_mapping, result_user_role_workspace_mapping if role_model and user_role_relation_model: # 获取当前用户的所有角色 判断是不是内置的系统管理员 is_admin = user_role_relation_model.objects.filter(user_id=user_id, role_id=RoleConstants.ADMIN.name).exists() user_ids = [user['id'] for user in result['records']] user_role_mapping, user_role_setting_mapping, user_role_workspace_mapping = _get_user_roles(user_ids, is_admin) # 将角色信息添加回用户数据中 for user in result['records']: user_id = str(user['id']) user['role_name'] = user_role_mapping.get(user_id, []) user['role_setting'] = user_role_setting_mapping.get(user_id, []) user['role_workspace'] = user_role_workspace_mapping.get(user_id, []) return result @transaction.atomic def save(self, instance, user_id, with_valid=True): if with_valid: if instance.get('encrypted'): instance['password'] = decrypt(instance.get('password')) self.UserInstance(data=instance).is_valid(raise_exception=True) user = User( id=uuid.uuid7(), email=instance.get('email'), phone=instance.get('phone', ''), nick_name=instance.get('nick_name', ''), username=instance.get('username'), password=password_encrypt(instance.get('password')), role=RoleConstants.USER.name, source=instance.get('source', 'LOCAL'), is_active=True ) update_user_role(instance, user, user_id) set_default_permission(user.id, instance) user.save() return UserInstanceSerializer(user).data class UserEditInstance(serializers.Serializer): email = serializers.EmailField( required=False, label=_("Email"), validators=[validators.EmailValidator( message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code )] ) nick_name = serializers.CharField( required=False, label=_("Name"), max_length=64, ) phone = serializers.CharField( required=False, label=_("Phone"), max_length=20, allow_null=True, allow_blank=True ) is_active = serializers.BooleanField( required=False, label=_("Is Active") ) def is_valid(self, *, user_id=None, raise_exception=False): super().is_valid(raise_exception=True) self._check_unique_email(user_id) self._check_unique_nick_name(user_id) def _check_unique_nick_name(self, user_id): nick_name = self.data.get('nick_name') if nick_name and User.objects.filter(nick_name=nick_name).exclude(id=user_id).exists(): raise AppApiException(1008, _('Nickname is already in use')) def _check_unique_email(self, user_id): email = self.data.get('email') if email and User.objects.filter(email=email).exclude(id=user_id).exists(): raise AppApiException(1004, _('Email is already in use')) class RePasswordInstance(serializers.Serializer): password = serializers.CharField( required=True, label=_("Password"), max_length=20, min_length=6, validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) re_password = serializers.CharField( required=True, label=_("Re Password"), validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) self._check_passwords_match() def _check_passwords_match(self): if self.data.get('password') != self.data.get('re_password'): raise ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.to_app_api_exception() class Operate(serializers.Serializer): id = serializers.UUIDField(required=True, label=_('User ID')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) self._check_user_exists() def _check_user_exists(self): if not User.objects.filter(id=self.data.get('id')).exists(): raise AppApiException(1004, _('User does not exist')) @transaction.atomic def delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) self._check_not_admin() user_id = self.data.get('id') # TODO 需要删除授权关系 User.objects.filter(id=user_id).delete() return True def _check_not_admin(self): user = User.objects.filter(id=self.data.get('id')).first() if user.role == RoleConstants.ADMIN.name or str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab': raise AppApiException(1004, _('Unable to delete administrator')) def edit(self, instance, user_id, with_valid=True): if with_valid: self.is_valid(raise_exception=True) UserManageSerializer.UserEditInstance(data=instance).is_valid(user_id=self.data.get('id'), raise_exception=True) user = User.objects.filter(id=self.data.get('id')).first() self._check_admin_modification(user, instance) self._update_user_fields(user, instance) update_user_role(instance, user, user_id) user.save() return UserInstanceSerializer(user).data @staticmethod def _check_admin_modification(user, instance): if user.role == RoleConstants.ADMIN.name and 'is_active' in instance and instance.get( 'is_active') is not None: raise AppApiException(1004, _('Cannot modify administrator status')) @staticmethod def _update_user_fields(user, instance): update_keys = ['email', 'nick_name', 'phone', 'is_active'] for key in update_keys: if key in instance and instance.get(key) is not None: setattr(user, key, instance.get(key)) def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) user = User.objects.filter(id=self.data.get('id')).first() workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") if workspace_user_role_mapping_model: role_setting = {} workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter( user_id=user.id) for workspace_user_role_mapping in workspace_user_role_mapping_list: role_id = workspace_user_role_mapping.role_id workspace_id = workspace_user_role_mapping.workspace_id if role_id not in role_setting: role_setting[role_id] = [] role_setting[role_id].append(workspace_id) return { 'id': user.id, 'username': user.username, 'email': user.email, 'phone': user.phone, 'nick_name': user.nick_name, 'is_active': user.is_active, 'role_setting': role_setting } return UserInstanceSerializer(user).data def re_password(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) UserManageSerializer.RePasswordInstance(data=instance).is_valid(raise_exception=True) user = User.objects.filter(id=self.data.get('id')).first() user.password = password_encrypt(instance.get('password')) user.save() return True def get_user_list(self, workspace_id): """ 获取用户列表 :param workspace_id: 工作空间ID :return: 用户列表 """ workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") if workspace_user_role_mapping_model: user_ids = ( workspace_user_role_mapping_model.objects .filter(workspace_id=workspace_id) .values_list('user_id', flat=True) .distinct() ) else: user_ids = User.objects.values_list('id', flat=True) users = User.objects.filter(id__in=user_ids).values('id', 'nick_name') return list(users) def get_user_members(self, workspace_id): """ 获取工作空间成员列表 :param workspace_id: 工作空间ID :return: 成员列表 """ role_model = DatabaseModelManage.get_model("role_model") user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping") if user_role_relation_model and role_model: user_role_relations = ( user_role_relation_model.objects .filter(workspace_id=workspace_id, role__type='USER') .select_related('role', 'user') ) user_dict = {} for relation in user_role_relations: user_id = relation.user.id if user_id not in user_dict: user_dict[user_id] = { 'id': user_id, 'nick_name': relation.user.nick_name, 'email': relation.user.email, 'roles': [relation.role.role_name] } else: user_dict[user_id]['roles'].append(relation.role.role_name) # 将字典值转换为列表形式 return list(user_dict.values()) user_list = User.objects.exclude(role=RoleConstants.ADMIN.name) return [ { 'id': user.id, 'nick_name': user.nick_name, 'email': user.email, 'roles': [RoleConstants.USER.name] } for user in user_list ] class BatchDelete(serializers.Serializer): ids = serializers.ListField(required=True, label=_('User IDs')) def batch_delete(self, with_valid=True): user_ids = self.data.get('ids') if not user_ids: raise AppApiException(1004, _('User IDs cannot be empty')) User.objects.filter(id__in=user_ids).exclude(id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab').delete() return True def get_all_user_list(self): users = User.objects.all().values('id', 'nick_name', 'username') return list(users) def update_user_role(instance, user, user_id=None): workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") if workspace_user_role_mapping_model: role_setting = instance.get('role_setting') license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False) license_is_valid = license_is_valid() if license_is_valid() is not None else False if not role_setting or (len(role_setting) == 1 and role_setting[0].get('role_id') == '' and len(role_setting[0].get('workspace_ids', [])) == 0): if not license_is_valid: workspace_user_role_mapping_model.objects.create( id=uuid.uuid7(), user_id=user.id, role_id=RoleConstants.USER.name, workspace_id='default' ) return is_admin = workspace_user_role_mapping_model.objects.filter(user_id=user_id, role_id=RoleConstants.ADMIN.name).exists() if str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab': # 需要判断当前角色的权限 不能删除系统管理员 空间管理员 普通管理员等角色 # role_setting是一个数组 结构式 [{role_id:1,workspace_ids:[1,2]}] # 如果role_id不包含ADMIN 就直接报错 如果WORKSPACE_MANAGE 或者USER 必须判断workspace_ids是否包含默认工作空间 不包含就报错 admin_role_id = RoleConstants.ADMIN.name workspace_manage_role_id = RoleConstants.WORKSPACE_MANAGE.name # 判断内置的三个角色是不是不在 current_role_ids = {item['role_id'] for item in role_setting} initial_role = [admin_role_id, workspace_manage_role_id, RoleConstants.USER.name] if not set(initial_role).issubset(current_role_ids): raise AppApiException(1004, _("Cannot delete built-in role")) if not any(item['role_id'] == str(admin_role_id) for item in role_setting): raise AppApiException(1004, _("Cannot delete built-in role")) # 验证 WORKSPACE_MANAGE 或 USER 是否包含默认工作空间 default_workspace_id = 'default' for item in role_setting: role_id = item['role_id'] workspace_ids = item.get('workspace_ids', []) if role_id == str(workspace_manage_role_id) or role_id == str(RoleConstants.USER.value): if default_workspace_id not in workspace_ids: raise AppApiException(1004, _("Cannot delete built-in role")) if is_admin: workspace_user_role_mapping_model.objects.filter(user_id=user.id).delete() else: workspace_user_role_mapping_model.objects.filter(user_id=user.id).exclude( role__type=RoleConstants.ADMIN.name).delete() relations = set() for item in role_setting: role_id = item['role_id'] workspace_ids = item['workspace_ids'] if item['workspace_ids'] else ['None'] for workspace_id in workspace_ids: relations.add((role_id, workspace_id)) for role_id, workspace_id in relations: workspace_user_role_mapping_model.objects.create( id=uuid.uuid7(), role_id=role_id, workspace_id=workspace_id, user_id=user.id ) permission_version, permission_get_key = Cache_Version.PERMISSION_LIST.value cache.delete(permission_get_key(str(user.id)), version=permission_version) def set_default_permission(user_id, instance): """ 为用户设置默认权限 """ default_permission = instance.get('defaultPermission', 'NOT_AUTH') # 获取工作空间ID列表 workspace_ids = _get_workspace_ids(instance, default_permission) if not workspace_ids: return # 根据权限类型确定认证类型 auth_type = (ResourceAuthType.ROLE if default_permission == ResourceAuthType.ROLE else ResourceAuthType.RESOURCE_PERMISSION_GROUP) # 设置根目录权限 _set_root_permissions(user_id, workspace_ids) # 如果是无权限设置,直接返回 if default_permission == 'NOT_AUTH': return # 设置具体资源权限 _set_resource_permissions(user_id, workspace_ids, default_permission, auth_type) def _get_workspace_ids(instance, default_permission): """ 获取工作空间ID列表 """ role_setting_model = DatabaseModelManage.get_model("role_model") if not role_setting_model: return ['default'] # 检查许可证有效性 license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False) if default_permission == ResourceAuthType.ROLE and not license_is_valid(): return [] role_setting = instance.get('role_setting') if not role_setting: return ['default'] # 获取用户角色的工作空间ID all_role_ids = [item['role_id'] for item in role_setting] user_role_ids = set(role_setting_model.objects.filter( id__in=all_role_ids, type=RoleConstants.USER.name ).values_list('id', flat=True)) workspace_ids = set() for item in role_setting: role_id = item['role_id'] if role_id in user_role_ids: workspace_ids.update(item.get('workspace_ids', [])) return list(workspace_ids) if workspace_ids else [] def _set_root_permissions(user_id, workspace_ids): """ 设置根目录权限(默认为查看权限) """ root_permissions = [] for ws in workspace_ids: root_permissions.extend([ WorkspaceUserResourcePermission( target=ws, auth_target_type=auth_target_type, permission_list=[ResourcePermission.VIEW], workspace_id=ws, user_id=user_id, auth_type=ResourceAuthType.RESOURCE_PERMISSION_GROUP ) for auth_target_type in [ AuthTargetType.APPLICATION.value, AuthTargetType.KNOWLEDGE.value, AuthTargetType.TOOL.value ] ]) _batch_create_permissions(root_permissions) def _set_resource_permissions(user_id, workspace_ids, default_permission, auth_type): """ 设置具体资源权限 """ # 批量查询资源并按工作空间分组 resource_maps = _get_resource_maps(workspace_ids) # 构造权限实例 instances = [] for ws in workspace_ids: instances.extend(_create_resource_permission_instances( ws, resource_maps, user_id, default_permission, auth_type)) # 批量创建权限 _batch_create_permissions(instances) def _get_resource_maps(workspace_ids): """ 获取各类型资源按工作空间的映射 """ from application.models import Application, ApplicationFolder from knowledge.models import Knowledge, KnowledgeFolder from tools.models import Tool, ToolFolder from models_provider.models import Model from collections import defaultdict resource_maps = { 'apps': defaultdict(list), 'app_folders': defaultdict(list), 'knowledge': defaultdict(list), 'knowledge_folders': defaultdict(list), 'tools': defaultdict(list), 'tool_folders': defaultdict(list), 'models': defaultdict(list) } # 查询应用资源 for ws, rid in Application.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['apps'][ws].append(rid) for ws, fid in ApplicationFolder.objects.filter(workspace_id__in=workspace_ids).exclude( id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['app_folders'][ws].append(fid) # 查询知识库资源 for ws, kid in Knowledge.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['knowledge'][ws].append(kid) for ws, kfid in KnowledgeFolder.objects.filter(workspace_id__in=workspace_ids).exclude( id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['knowledge_folders'][ws].append(kfid) # 查询工具资源 for ws, tid in Tool.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['tools'][ws].append(tid) for ws, tfid in ToolFolder.objects.filter(workspace_id__in=workspace_ids).exclude( id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['tool_folders'][ws].append(tfid) # 查询模型资源 for ws, mid in Model.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'): resource_maps['models'][ws].append(mid) return resource_maps def _create_resource_permission_instances(workspace_id, resource_maps, user_id, permission, auth_type): """ 创建资源权限实例列表 """ instances = [] if permission == ResourcePermission.MANAGE: permission = [ResourcePermission.VIEW, ResourcePermission.MANAGE] else: permission = [permission] # 应用权限 for rid in resource_maps['apps'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=rid, auth_target_type=AuthTargetType.APPLICATION.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 应用文件夹权限 for fid in resource_maps['app_folders'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=fid, auth_target_type=AuthTargetType.APPLICATION.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 知识库权限 for kid in resource_maps['knowledge'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=kid, auth_target_type=AuthTargetType.KNOWLEDGE.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 知识库文件夹权限 for kf in resource_maps['knowledge_folders'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=kf, auth_target_type=AuthTargetType.KNOWLEDGE.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 工具权限 for tid in resource_maps['tools'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=tid, auth_target_type=AuthTargetType.TOOL.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 工具文件夹权限 for tf in resource_maps['tool_folders'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=tf, auth_target_type=AuthTargetType.TOOL.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) # 模型权限 for mid in resource_maps['models'].get(workspace_id, []): instances.append(WorkspaceUserResourcePermission( target=mid, auth_target_type=AuthTargetType.MODEL.value, permission_list=permission, workspace_id=workspace_id, user_id=user_id, auth_type=auth_type )) return instances def _batch_create_permissions(instances, batch_size=500): """ 批量创建权限实例 """ if not instances: return objs = WorkspaceUserResourcePermission.objects for i in range(0, len(instances), batch_size): objs.bulk_create(instances[i:i + batch_size]) class RePasswordSerializer(serializers.Serializer): email = serializers.EmailField( required=True, label=_("Email"), validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) code = serializers.CharField(required=True, label=_("Code")) password = serializers.CharField( required=True, label=_("Password"), max_length=20, min_length=6, validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) re_password = serializers.CharField( required=True, label=_("Re Password"), validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) class Meta: model = User fields = '__all__' def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) email = self.data.get("email") cache_code = cache.get(get_key(email + ':reset_password'), version=version) if self.data.get('password') != self.data.get('re_password'): raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code, ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message) if cache_code != self.data.get('code'): raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code, ExceptionCodeConstants.CODE_ERROR.value.message) return True def reset_password(self): """ 修改密码 :return: 是否成功 """ if self.is_valid(): email = self.data.get("email") QuerySet(User).filter(email=email).update( password=password_encrypt(self.data.get('password'))) code_cache_key = email + ":reset_password" cache.delete(get_key(code_cache_key), version=version) return True class ResetCurrentUserPassword(serializers.Serializer): password = serializers.CharField( required=True, label=_("Password"), max_length=20, min_length=6, validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) re_password = serializers.CharField( required=True, label=_("Re Password"), validators=[ validators.RegexValidator( regex=PASSWORD_REGEX, message=_( "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters." ) ) ] ) class Meta: model = User fields = '__all__' def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) if self.data.get('password') != self.data.get('re_password'): raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code, ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message) return True def reset_password(self, user_id: str): """ 修改密码 :return: 是否成功 """ if self.is_valid(): QuerySet(User).filter(id=user_id).update( password=password_encrypt(self.data.get('password'))) return True class SendEmailSerializer(serializers.Serializer): email = serializers.EmailField( required=True , label=_("Email"), validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) type = serializers.CharField(required=True, label=_("Type"), validators=[ validators.RegexValidator(regex=re.compile("^register|reset_password$"), message=_("The type only supports register|reset_password"), code=500) ]) class Meta: model = User fields = '__all__' def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=raise_exception) code_cache_key = self.data.get('email') + ":" + self.data.get("type") code_cache_key_lock = code_cache_key + "_lock" ttl = cache.ttl(code_cache_key_lock, version=version) if ttl is not None and ttl > 0: raise AppApiException(500, _("Do not send emails again within {seconds} seconds").format( seconds=int(ttl.total_seconds()))) return True def send(self): """ 发送邮件 :return: 是否发送成功 :exception 发送失败异常 """ email = self.data.get("email") state = self.data.get("type") # 生成随机验证码 code = "".join(list(map(lambda i: random.choice(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0' ]), range(6)))) # 获取邮件模板 language = get_language() file = open( os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), "r", encoding='utf-8') content = file.read() file.close() code_cache_key = email + ":" + state code_cache_key_lock = code_cache_key + "_lock" # 设置缓存 cache.set(get_key(code_cache_key_lock), code, timeout=60, version=version) system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first() if system_setting is None: cache.delete(get_key(code_cache_key_lock), version=version) raise AppApiException(1004, _("The email service has not been set up. Please contact the administrator to set up the email service in [Email Settings].")) try: connection = EmailBackend(system_setting.meta.get("email_host"), system_setting.meta.get('email_port'), system_setting.meta.get('email_host_user'), system_setting.meta.get('email_host_password'), system_setting.meta.get('email_use_tls'), False, system_setting.meta.get('email_use_ssl') ) # 发送邮件 send_mail(_('【Intelligent knowledge base question and answer system-{action}】').format( action=_('User registration') if state == 'register' else _('Change password')), '', html_message=f'{content.replace("${code}", code)}', from_email=system_setting.meta.get('from_email'), recipient_list=[email], fail_silently=False, connection=connection) except Exception as e: cache.delete(get_key(code_cache_key_lock)) return True cache.set(get_key(code_cache_key), code, timeout=60 * 30, version=version) return True class CheckCodeSerializer(serializers.Serializer): """ 校验验证码 """ email = serializers.EmailField( required=True, label=_("Email"), validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) code = serializers.CharField(required=True, label=_("Verification code")) type = serializers.CharField(required=True, label=_("Type"), validators=[ validators.RegexValidator(regex=re.compile("^register|reset_password$"), message=_( "The type only supports register|reset_password"), code=500) ]) def is_valid(self, *, raise_exception=False): super().is_valid() value = cache.get(get_key(self.data.get("email") + ":" + self.data.get("type")), version=version) if value is None or value != self.data.get("code"): raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception() return True class SwitchLanguageSerializer(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) language = serializers.CharField(required=True, label=_('language')) def switch(self): self.is_valid(raise_exception=True) language = self.data.get('language') support_language_list = ['zh-CN', 'zh-Hant', 'en-US'] if not support_language_list.__contains__(language): raise AppApiException(500, _('language only support:') + ','.join(support_language_list)) QuerySet(User).filter(id=self.data.get('user_id')).update(language=language) ================================================ FILE: apps/users/tests.py ================================================ from django.test import TestCase # Create your tests here. ================================================ FILE: apps/users/urls.py ================================================ from django.urls import path from . import views app_name = "user" # @formatter:off urlpatterns = [ path('user/login', views.LoginView.as_view(), name='login'), path('user/profile', views.UserProfileView.as_view(), name="user_profile"), path('user/captcha', views.CaptchaView.as_view(), name='captcha'), path('user/test', views.TestPermissionsUserView.as_view(), name="test"), path('user/logout', views.Logout.as_view(), name='logout'), path('user/language', views.SwitchUserLanguageView.as_view(), name='language'), path("user/send_email", views.SendEmail.as_view(), name='send_email'), path("user/check_code", views.CheckCode.as_view(), name='check_code'), path("user/re_password", views.RePasswordView.as_view(), name='re_password'), path("user/current/send_email", views.SendEmailToCurrentUserView.as_view(), name="send_email_current"), path("user/current/reset_password", views.ResetCurrentUserPasswordView.as_view(), name="reset_password_current"), path("user/list", views.UserList.as_view(), name="current_user_profile"), path('workspace//user_list', views.WorkspaceUserListView.as_view(), name="test_workspace_id_permission"), path('workspace//user_member',views.WorkspaceUserMemberView.as_view(), name="test_workspace_id_permission"), path('workspace//user/profile', views.TestWorkspacePermissionUserView.as_view(), name="test_workspace_id_permission"), path("user_manage", views.UserManage.as_view(), name="user_manage"), path("user_manage/batch_delete", views.UserManage.BatchDelete.as_view()), path("user_manage/password", views.UserManage.Password.as_view()), path("user_manage/", views.UserManage.Operate.as_view(), name="user_manage_operate"), path("user_manage//re_password", views.UserManage.RePassword.as_view(), name="user_manage_re_password"), path("user_manage//", views.UserManage.Page.as_view(), name="user_manage_page"), ] ================================================ FILE: apps/users/views/__init__.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: __init__.py.py @date:2025/4/14 10:20 @desc: """ from .login import * from .user import * ================================================ FILE: apps/users/views/login.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 10:22 @desc: """ from django.core.cache import cache from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common import result from common.auth import TokenAuth from common.constants.cache_version import Cache_Version from common.log.log import log from common.utils.common import encryption from models_provider.api.model import DefaultModelResponse from users.api.login import LoginAPI, CaptchaAPI from users.serializers.login import LoginSerializer, CaptchaSerializer def _get_details(request): path = request.path body = request.data query = request.query_params return { 'path': path, 'body': {**body, 'password': encryption(body.get('password', ''))}, 'query': query } class LoginView(APIView): @extend_schema(methods=['POST'], description=_("Log in"), summary=_("Log in"), operation_id=_("Log in"), # type: ignore tags=[_("User Management")], # type: ignore request=LoginAPI.get_request(), responses=LoginAPI.get_response()) @log(menu='User management', operate='Log in', get_user=lambda r: {'username': r.data.get('username', None)}, get_details=_get_details, get_operation_object=lambda r, k: {'name': r.data.get('username')}) def post(self, request: Request): return result.success(LoginSerializer().login(request.data)) class Logout(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Sign out"), description=_("Sign out"), operation_id=_("Sign out"), # type: ignore tags=[_("User Management")], # type: ignore responses=DefaultModelResponse.get_response()) @log(menu='User management', operate='Sign out', get_operation_object=lambda r, k: {'name': r.user.username}) def post(self, request: Request): version, get_key = Cache_Version.TOKEN.value cache.delete(get_key(token=request.META.get('HTTP_AUTHORIZATION')[7:]), version=version) return result.success(True) class CaptchaView(APIView): @extend_schema(methods=['GET'], summary=_("Get captcha"), description=_("Get captcha"), operation_id=_("Get captcha"), # type: ignore tags=[_("User Management")], # type: ignore responses=CaptchaAPI.get_response()) def get(self, request: Request): username = request.query_params.get('username', None) return result.success(CaptchaSerializer().generate(username)) ================================================ FILE: apps/users/views/user.py ================================================ # coding=utf-8 """ @project: MaxKB @Author:虎虎 @file: user.py @date:2025/4/14 19:25 @desc: """ from django.core.cache import cache from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView from common.auth.authenticate import TokenAuth from common.auth.authentication import has_permissions from common.constants.cache_version import Cache_Version from common.constants.permission_constants import PermissionConstants, Permission, Group, Operate, RoleConstants from common.log.log import log from common.result import result from common.utils.common import query_params_to_single_dict from maxkb.const import CONFIG from models_provider.api.model import DefaultModelResponse from tools.serializers.tool import encryption from users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi, DeleteUserApi, EditUserApi, \ ChangeUserPasswordApi, UserPageApi, UserListApi, UserPasswordResponse, WorkspaceUserAPI, ResetPasswordAPI, \ SendEmailAPI, CheckCodeAPI, SwitchUserLanguageAPI from users.models import User from users.serializers.user import UserProfileSerializer, UserManageSerializer, CheckCodeSerializer, \ SendEmailSerializer, RePasswordSerializer, SwitchLanguageSerializer, ResetCurrentUserPassword default_password = CONFIG.get('DEFAULT_PASSWORD', 'MaxKB@123..') def get_user_operation_object(user_id): user_model = QuerySet(model=User).filter(id=user_id).first() if user_model is not None: return { "name": user_model.name } return {} def get_re_password_details(request): path = request.path body = request.data query = request.query_params return { "path": path, "body": {**body, 'password': encryption(body.get('password', '')), 're_password': encryption(body.get('re_password', ''))}, "query": query } class UserProfileView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get current user information"), description=_("Get current user information"), operation_id=_("Get current user information"), # type: ignore tags=[_("User Management")], # type: ignore responses=UserProfileAPI.get_response()) def get(self, request: Request): return result.success(UserProfileSerializer().profile(request.user, request.auth)) class TestPermissionsUserView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get current user information"), description=_("Get current user information"), operation_id="测试", tags=[_("User Management")], # type: ignore responses=UserProfileAPI.get_response()) @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) def get(self, request: Request): return result.success(UserProfileSerializer().profile(request.user, request.auth)) class SwitchUserLanguageView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Switch Language"), description=_("Switch Language"), operation_id=_("Switch Language"), # type: ignore tags=[_("User Management")], # type: ignore request=SwitchUserLanguageAPI.get_request(), ) @log(menu='User management', operate='Switch Language', get_operation_object=lambda r, k: {'name': r.user.username}) @has_permissions(PermissionConstants.SWITCH_LANGUAGE, RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.WORKSPACE_MANAGE) def post(self, request: Request): data = {**request.data, 'user_id': request.user.id} return result.success(SwitchLanguageSerializer(data=data).switch()) class TestWorkspacePermissionUserView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary="针对工作空间下权限校验", description="针对工作空间下权限校验", operation_id="针对工作空间下权限校验", tags=[_("User Management")], # type: ignore responses=UserProfileAPI.get_response(), parameters=TestWorkspacePermissionUserApi.get_parameters()) @has_permissions(PermissionConstants.USER_EDIT.get_workspace_permission(), RoleConstants.ADMIN) def get(self, request: Request, workspace_id): return result.success(UserProfileSerializer().profile(request.user, request.auth)) class UserList(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get all user"), description=_("Get all user"), operation_id=_("Get all user"), # type: ignore tags=[_("User Management")], # type: ignore responses=UserListApi.get_response()) @has_permissions(RoleConstants.WORKSPACE_MANAGE, RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN, RoleConstants.EXTENDS_WORKSPACE_MANAGE) def get(self, request: Request): return result.success(UserManageSerializer().get_all_user_list()) class WorkspaceUserListView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get user list under workspace"), description=_("Get user list under workspace"), operation_id=_("Get user list under workspace"), # type: ignore tags=[_("User Management")], # type: ignore parameters=WorkspaceUserAPI.get_parameters(), responses=WorkspaceUserAPI.get_response()) def get(self, request: Request, workspace_id): return result.success(UserManageSerializer().get_user_list(workspace_id)) class WorkspaceUserMemberView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get user member under workspace"), description=_("Get user member under workspace"), operation_id=_("Get user member under workspace"), # type: ignore tags=[_("User Management")], # type: ignore parameters=WorkspaceUserAPI.get_parameters(), responses=WorkspaceUserAPI.get_response()) def get(self, request: Request, workspace_id): return result.success(UserManageSerializer().get_user_members(workspace_id)) class UserManage(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Create user"), description=_("Create user"), operation_id=_("Create user"), # type: ignore tags=[_("User Management")], # type: ignore request=UserProfileAPI.get_request(), responses=UserProfileAPI.get_response()) @has_permissions(PermissionConstants.USER_CREATE, RoleConstants.ADMIN) @log(menu='User management', operate='Add user', get_operation_object=lambda r, k: {'name': r.data.get('username', None)}) def post(self, request: Request): return result.success(UserManageSerializer().save(request.data, str(request.user.id))) class Password(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['Get'], summary=_("Get default password"), description=_("Get default password"), operation_id=_("Get default password"), # type: ignore tags=[_("User Management")], # type: ignore responses=UserPasswordResponse.get_response()) @has_permissions(PermissionConstants.USER_CREATE, PermissionConstants.CHAT_USER_CREATE, PermissionConstants.WORKSPACE_CHAT_USER_CREATE, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE) def get(self, request: Request): return result.success(data={'password': default_password}) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['DELETE'], description=_("Delete user"), summary=_("Delete user"), operation_id=_("Delete user"), # type: ignore tags=[_("User Management")], # type: ignore parameters=DeleteUserApi.get_parameters(), responses=DefaultModelResponse.get_response()) @has_permissions(PermissionConstants.USER_DELETE, RoleConstants.ADMIN) @log(menu='User management', operate='Delete user', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id'))) def delete(self, request: Request, user_id): return result.success(UserManageSerializer.Operate(data={'id': user_id}).delete(with_valid=True)) @extend_schema(methods=['GET'], summary=_("Get user information"), description=_("Get user information"), operation_id=_("Get user information"), # type: ignore tags=[_("User Management")], # type: ignore request=DeleteUserApi.get_parameters(), responses=UserProfileAPI.get_response()) @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN) def get(self, request: Request, user_id): return result.success(UserManageSerializer.Operate(data={'id': user_id}).one(with_valid=True)) @extend_schema(methods=['PUT'], summary=_("Update user information"), description=_("Update user information"), operation_id=_("Update user information"), # type: ignore tags=[_("User Management")], # type: ignore parameters=DeleteUserApi.get_parameters(), request=EditUserApi.get_request(), responses=UserProfileAPI.get_response()) @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) @log(menu='User management', operate='Update user information', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id'))) def put(self, request: Request, user_id): return result.success( UserManageSerializer.Operate(data={'id': user_id}).edit(request.data, str(request.user.id), with_valid=True)) class BatchDelete(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], description=_("Batch delete user"), summary=_("Batch delete user"), operation_id=_("Batch delete user"), # type: ignore tags=[_("User Management")], # type: ignore request=DeleteUserApi.get_request(), responses=DefaultModelResponse.get_response()) @has_permissions(PermissionConstants.USER_DELETE, RoleConstants.ADMIN) @log(menu='User management', operate='Batch delete user', get_operation_object=lambda r, k: get_user_operation_object(r.data.get('ids', []))) def post(self, request: Request): return result.success(UserManageSerializer.BatchDelete({'ids': request.data}).batch_delete(with_valid=True)) class RePassword(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['PUT'], summary=_("Change password"), description=_("Change password"), operation_id=_("Change password"), # type: ignore tags=[_("User Management")], # type: ignore parameters=DeleteUserApi.get_parameters(), request=ChangeUserPasswordApi.get_request(), responses=DefaultModelResponse.get_response()) @log(menu='User management', operate='Change password', get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')), get_details=get_re_password_details) @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) def put(self, request: Request, user_id): return result.success( UserManageSerializer.Operate(data={'id': user_id}).re_password(request.data, with_valid=True)) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['GET'], summary=_("Get user paginated list"), description=_("Get user paginated list"), operation_id=_("Get user paginated list"), # type: ignore tags=[_("User Management")], # type: ignore parameters=UserPageApi.get_parameters(), responses=UserPageApi.get_response()) @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN) def get(self, request: Request, current_page, page_size): d = UserManageSerializer.Query( data={**query_params_to_single_dict(request.query_params)}) return result.success(d.page(current_page, page_size, str(request.user.id))) class RePasswordView(APIView): @extend_schema(methods=['POST'], summary=_("Change password"), description=_("Change password"), operation_id=_("Change password"), # type: ignore tags=[_("User Management")], # type: ignore request=ResetPasswordAPI.get_request(), responses=DefaultModelResponse.get_response()) @log(menu='User management', operate='Change password', get_operation_object=lambda r, k: {'name': r.user.username}, get_details=get_re_password_details) def post(self, request: Request): serializer_obj = RePasswordSerializer(data=request.data) return result.success(serializer_obj.reset_password()) class SendEmail(APIView): @extend_schema(methods=['POST'], summary=_("Send email"), description=_("Send email"), operation_id=_("Send email"), # type: ignore tags=[_("User Management")], # type: ignore request=SendEmailAPI.get_request(), responses=SendEmailAPI.get_response()) @log(menu='User management', operate='Send email', get_operation_object=lambda r, k: {'name': r.data.get('email', None)}, get_user=lambda r: {'user_name': None, 'email': r.data.get('email', None)}) def post(self, request: Request): serializer_obj = SendEmailSerializer(data=request.data) if serializer_obj.is_valid(raise_exception=True): return result.success(serializer_obj.send()) class CheckCode(APIView): @extend_schema(methods=['POST'], summary=_("Check whether the verification code is correct"), description=_("Check whether the verification code is correct"), operation_id=_("Check whether the verification code is correct"), # type: ignore tags=[_("User Management")], # type: ignore request=CheckCodeAPI.get_request(), responses=CheckCodeAPI.get_response()) @log(menu='User management', operate='Check whether the verification code is correct', get_operation_object=lambda r, k: {'name': r.data.get('email', None)}, get_user=lambda r: {'user_name': None, 'email': r.data.get('email', None)}) def post(self, request: Request): return result.success(CheckCodeSerializer(data=request.data).is_valid(raise_exception=True)) class SendEmailToCurrentUserView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Send email to current user"), description=_("Send email to current user"), operation_id=_("Send email to current user"), # type: ignore tags=[_("User Management")], # type: ignore request=SendEmailAPI.get_request(), responses=SendEmailAPI.get_response()) @log(menu='User management', operate='Send email to current user', get_operation_object=lambda r, k: {'name': r.user.username}) def post(self, request: Request): serializer_obj = SendEmailSerializer(data={'email': request.user.email, 'type': "reset_password"}) if serializer_obj.is_valid(raise_exception=True): return result.success(serializer_obj.send()) class ResetCurrentUserPasswordView(APIView): authentication_classes = [TokenAuth] @extend_schema(methods=['POST'], summary=_("Modify current user password"), description=_("Modify current user password"), operation_id=_("Modify current user password"), # type: ignore tags=[_("User Management")], # type: ignore request=ResetPasswordAPI.get_request(), responses=DefaultModelResponse.get_response()) @log(menu='User management', operate='Modify current user password', get_operation_object=lambda r, k: {'name': r.user.username}, get_details=get_re_password_details) @has_permissions(PermissionConstants.CHANGE_PASSWORD, RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.WORKSPACE_MANAGE) def post(self, request: Request): serializer_obj = ResetCurrentUserPassword(data=request.data) if serializer_obj.reset_password(request.user.id): version, get_key = Cache_Version.TOKEN.value cache.delete(get_key(token=request.auth), version=version) return result.success(True) return result.error(_("Failed to change password")) ================================================ FILE: installer/Dockerfile ================================================ FROM node:24-alpine AS web-build COPY ui ui RUN cd ui && ls -la && if [ -d "dist" ]; then exit 0; fi && \ npm install --prefer-offline --no-audit && \ npm install -D concurrently && \ NODE_OPTIONS="--max-old-space-size=4096" npx concurrently "npm run build" "npm run build-chat" && \ find . -maxdepth 1 ! -name '.' ! -name 'dist' ! -name 'public' -exec rm -rf {} + FROM ghcr.io/1panel-dev/maxkb-base:python3.11-pg17.7-20260212 AS stage-build COPY --chmod=700 . /opt/maxkb-app RUN apt-get update && \ apt-get install -y --no-install-recommends gcc g++ gettext libexpat1-dev libffi-dev && \ apt-get clean all && \ rm -rf /var/lib/apt/lists/* WORKDIR /opt/maxkb-app RUN gcc -shared -fPIC -o ${MAXKB_SANDBOX_HOME}/lib/sandbox.so /opt/maxkb-app/installer/sandbox.c -ldl && \ rm -rf /opt/maxkb-app/ui && \ pip install uv --break-system-packages && \ python -m uv pip install -r pyproject.toml && \ find /opt/maxkb-app -depth \( -name ".git*" -o -name ".docker*" -o -name ".idea*" -o -name ".editorconfig*" -o -name ".prettierrc*" -o -name "README.md" -o -name "poetry.lock" -o -name "pyproject.toml" \) -exec rm -rf {} + && \ python /opt/maxkb-app/apps/manage.py compilemessages && \ export PIP_TARGET=/opt/maxkb-app/sandbox/python-packages && \ python -m uv pip install --target=$PIP_TARGET requests pymysql psycopg2-binary && \ rm -rf /opt/maxkb-app/installer COPY --from=web-build --chmod=700 ui /opt/maxkb-app/ui FROM ghcr.io/1panel-dev/maxkb-base:python3.11-pg17.7-20260212 ARG DOCKER_IMAGE_TAG=dev \ BUILD_AT \ GITHUB_COMMIT ENV MAXKB_VERSION="${DOCKER_IMAGE_TAG} (build at ${BUILD_AT}, commit: ${GITHUB_COMMIT})" \ MAXKB_DB_NAME=maxkb \ MAXKB_DB_HOST=127.0.0.1 \ MAXKB_DB_PORT=5432 \ MAXKB_DB_USER=${POSTGRES_USER} \ MAXKB_DB_PASSWORD=${POSTGRES_PASSWORD} \ MAXKB_DB_MAX_OVERFLOW=80 \ MAXKB_REDIS_HOST=127.0.0.1 \ MAXKB_REDIS_PORT=6379 \ MAXKB_REDIS_DB=0 \ MAXKB_REDIS_PASSWORD=${REDIS_PASSWORD} \ MAXKB_EMBEDDING_MODEL_PATH=/opt/maxkb-app/model/embedding \ MAXKB_EMBEDDING_MODEL_NAME=/opt/maxkb-app/model/embedding/shibing624_text2vec-base-chinese \ MAXKB_LOCAL_MODEL_HOST=127.0.0.1 \ MAXKB_LOCAL_MODEL_PORT=11636 \ MAXKB_LOCAL_MODEL_PROTOCOL=http \ PIP_TARGET=/opt/maxkb/python-packages WORKDIR /opt/maxkb-app COPY --from=stage-build /opt/maxkb-app /opt/maxkb-app COPY --from=stage-build /opt/py3 /opt/py3 EXPOSE 8080 VOLUME /opt/maxkb ENTRYPOINT ["bash", "-c"] CMD [ "/usr/bin/start-all.sh" ] ================================================ FILE: installer/Dockerfile-base ================================================ FROM python:3.11-slim-trixie AS python-stage RUN python3 -m venv /opt/py3 FROM ghcr.io/1panel-dev/maxkb-vector-model:v2.0.3 AS vector-model FROM postgres:17.7-trixie COPY --from=python-stage /usr/local /usr/local COPY --from=python-stage /opt/py3 /opt/py3 COPY --chmod=500 installer/*.sh /usr/bin/ COPY installer/init.sql /docker-entrypoint-initdb.d/ ARG DEPENDENCIES=" \ curl \ ca-certificates \ vim \ wait-for-it \ redis-server \ postgresql-17-pgvector \ postgresql-17-age" RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ echo "deb http://deb.debian.org/debian testing main" >> /etc/apt/sources.list && \ printf "Package: redis-server\nPin: release a=testing\nPin-Priority: 501\n" > /etc/apt/preferences.d/redis && \ apt-get update && apt-get install -y --no-install-recommends $DEPENDENCIES && \ find /etc/ -type f ! -path '/etc/resolv.conf' ! -path '/etc/hosts' | xargs chmod g-rx && \ curl -L --connect-timeout 120 -m 1800 https://resource.fit2cloud.com/maxkb/ffmpeg/get-ffmpeg-linux | sh && \ mkdir -p /opt/maxkb-app/sandbox/lib && chmod -R 550 /opt/maxkb-app/sandbox && \ useradd --no-create-home --home /opt/maxkb-app/sandbox -s /usr/sbin/nologin sandbox -g root && \ chmod g-rwx /usr/local/bin/* /usr/bin/* /bin/* /usr/sbin/* /sbin/* /usr/lib/postgresql/17/bin/* && \ chmod g+xr /usr/bin/ld.so /usr/local/bin/python* `which env` && \ chmod -R g-rwx /tmp /var/tmp /var/lock && \ chmod g+rx /tmp && \ apt-get clean all && \ rm -rf /var/lib/postgresql /var/lib/apt/lists/* /usr/share/doc/* /usr/share/man/* /usr/share/info/* /usr/share/locale/* /usr/share/lintian/* /usr/share/linda/* /var/cache/* /var/log/* /var/tmp/* /tmp/* COPY --from=vector-model --chmod=700 /opt/maxkb-app/model /opt/maxkb-app/model ENV PATH=/opt/py3/bin:$PATH \ PGDATA=/opt/maxkb/data/postgresql/pgdata \ POSTGRES_USER=root \ POSTGRES_PASSWORD=Password123@postgres \ POSTGRES_MAX_CONNECTIONS=1000 \ REDIS_PASSWORD=Password123@redis \ LANG=en_US.UTF-8 \ PYTHONUNBUFFERED=1 \ MAXKB_CONFIG_TYPE=ENV \ MAXKB_LOG_LEVEL=INFO \ MAXKB_SANDBOX=1 \ MAXKB_SANDBOX_HOME=/opt/maxkb-app/sandbox \ MAXKB_SANDBOX_PYTHON_PACKAGE_PATHS="/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages" \ MAXKB_SANDBOX_PYTHON_BANNED_HOSTS="127.0.0.0/8,localhost,host.docker.internal,172.17.0.0/16,maxkb,pgsql,redis,172.31.250.192/26,0.0.0.0/32,::/0" \ MAXKB_ADMIN_PATH=/admin EXPOSE 6379 ================================================ FILE: installer/Dockerfile-vector-model ================================================ #FROM python:3.11-slim-bookworm AS vector-model #COPY installer/install_model.py install_model.py #RUN pip3 install --upgrade pip setuptools && \ # pip install pycrawlers && \ # pip install transformers && \ # python3 install_model.py && \ # cp -r model/base/hub model/tokenizer #FROM scratch #COPY --from=vector-model model /opt/maxkb-app/model # 不知道为什么用上面的脚本重新拉一遍向量模型比之前的大很多,所以还是用下面的脚本复用原来已经构建好的向量模型 FROM python:3.11-slim-bookworm AS tmp-stage1 COPY installer/install_model_bert_base_cased.py install_model_bert_base_cased.py RUN pip3 install --upgrade pip setuptools && \ pip install pycrawlers && \ pip install transformers && \ python3 install_model_bert_base_cased.py && \ cp -r model/base/hub model/tokenizer FROM ghcr.io/1panel-dev/maxkb-vector-model:v1.0.1 AS vector-model FROM alpine AS tmp-stage2 COPY --from=vector-model /opt/maxkb/app/model /opt/maxkb-app/model COPY --from=vector-model /opt/maxkb/app/model/base/hub /opt/maxkb-app/model/tokenizer COPY --from=tmp-stage1 model/tokenizer /opt/maxkb-app/model/tokenizer RUN rm -rf /opt/maxkb-app/model/embedding/shibing624_text2vec-base-chinese/onnx RUN apk add --update --no-cache curl && \ mkdir -p openai-tiktoken-cl100k-base && \ curl -Lf https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken > openai-tiktoken-cl100k-base/cl100k_base.tiktoken && \ mv -f openai-tiktoken-cl100k-base /opt/maxkb-app/model/tokenizer/ FROM scratch COPY --from=tmp-stage2 /opt/maxkb-app/model /opt/maxkb-app/model ================================================ FILE: installer/init.sql ================================================ CREATE DATABASE "maxkb"; \c "maxkb"; CREATE EXTENSION "vector"; ================================================ FILE: installer/install_model.py ================================================ # coding=utf-8 """ @project: maxkb @Author:虎 @file: install_model.py @date:2023/12/18 14:02 @desc: """ import json import os.path from pycrawlers import huggingface from transformers import GPT2TokenizerFast hg = huggingface() prefix_dir = "./model" model_config = [ { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'gpt2' }, 'download_function': GPT2TokenizerFast.from_pretrained }, { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'gpt2-medium' }, 'download_function': GPT2TokenizerFast.from_pretrained }, { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'gpt2-large' }, 'download_function': GPT2TokenizerFast.from_pretrained }, { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'gpt2-xl' }, 'download_function': GPT2TokenizerFast.from_pretrained }, { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'distilgpt2' }, 'download_function': GPT2TokenizerFast.from_pretrained }, { 'download_params': { 'urls': ["https://huggingface.co/shibing624/text2vec-base-chinese/tree/main"], 'file_save_paths': [os.path.join(prefix_dir, 'embedding',"shibing624_text2vec-base-chinese")] }, 'download_function': hg.get_batch_data } ] def install(): for model in model_config: print(json.dumps(model.get('download_params'))) model.get('download_function')(**model.get('download_params')) if __name__ == '__main__': install() ================================================ FILE: installer/install_model_bert_base_cased.py ================================================ # coding=utf-8 import json import os.path from transformers import BertTokenizer prefix_dir = "./model" model_config = [ { 'download_params': { 'cache_dir': os.path.join(prefix_dir, 'base/hub'), 'pretrained_model_name_or_path': 'bert-base-cased' }, 'download_function': BertTokenizer.from_pretrained }, ] def install(): for model in model_config: print(json.dumps(model.get('download_params'))) model.get('download_function')(**model.get('download_params')) if __name__ == '__main__': install() ================================================ FILE: installer/sandbox.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CONFIG_FILE ".sandbox.conf" #define KEY_BANNED_HOSTS "SANDBOX_PYTHON_BANNED_HOSTS" #define KEY_ALLOW_DL_PATHS "SANDBOX_PYTHON_ALLOW_DL_PATHS" #define KEY_ALLOW_SUBPROCESS "SANDBOX_PYTHON_ALLOW_SUBPROCESS" #define KEY_ALLOW_SYSCALL "SANDBOX_PYTHON_ALLOW_SYSCALL" static char *banned_hosts = NULL; static char *allow_dl_paths = NULL; static int allow_subprocess = 0; // 默认禁止 static int allow_syscall = 0; static void load_sandbox_config() { Dl_info info; if (dladdr((void *)load_sandbox_config, &info) == 0 || !info.dli_fname) { banned_hosts = strdup(""); allow_dl_paths = strdup(""); allow_subprocess = 0; allow_syscall = 0; return; } char so_path[PATH_MAX]; strncpy(so_path, info.dli_fname, sizeof(so_path)); so_path[sizeof(so_path) - 1] = '\0'; char *dir = dirname(so_path); char config_path[PATH_MAX]; snprintf(config_path, sizeof(config_path), "%s/%s", dir, CONFIG_FILE); FILE *fp = fopen(config_path, "r"); if (!fp) { banned_hosts = strdup(""); allow_dl_paths = strdup(""); allow_subprocess = 0; allow_syscall = 0; return; } char line[512]; if (banned_hosts) { free(banned_hosts); banned_hosts = NULL; } if (allow_dl_paths) { free(allow_dl_paths); allow_dl_paths = NULL; } banned_hosts = strdup(""); allow_dl_paths = strdup(""); allow_subprocess = 0; allow_syscall = 0; while (fgets(line, sizeof(line), fp)) { char *key = strtok(line, "="); char *value = strtok(NULL, "\n"); if (!key || !value) continue; while (*key == ' ' || *key == '\t') key++; char *keyend = key + strlen(key) - 1; while (keyend > key && (*keyend == ' ' || *keyend == '\t')) *keyend-- = '\0'; while (*value == ' ' || *value == '\t') value++; char *vend = value + strlen(value) - 1; while (vend > value && (*vend == ' ' || *vend == '\t')) *vend-- = '\0'; if (strcmp(key, KEY_BANNED_HOSTS) == 0) { free(banned_hosts); banned_hosts = strdup(value); } else if (strcmp(key, KEY_ALLOW_DL_PATHS) == 0) { free(allow_dl_paths); allow_dl_paths = strdup(value); // 逗号分隔字符串 } else if (strcmp(key, KEY_ALLOW_SUBPROCESS) == 0) { allow_subprocess = atoi(value); } else if (strcmp(key, KEY_ALLOW_SYSCALL) == 0) { allow_syscall = atoi(value); } } fclose(fp); } static void ensure_config_loaded() { if (!banned_hosts) load_sandbox_config(); } static int is_sandbox_user() { uid_t uid = getuid(); struct passwd *pw = getpwuid(uid); if (!pw || !pw->pw_name) { return 1; // 无法识别用户 → 认为是 sandbox } if (strcmp(pw->pw_name, "sandbox") == 0) { return 1; } return 0; } static int throw_permission_denied_err(bool whether_to_exit,const char *fmt, ...) { va_list ap; fputs("Permission denied to ", stderr); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputs(".\n", stderr); errno = EACCES; if (whether_to_exit) _exit(126); return -1; } #define RESOLVE_REAL(func) \ static typeof(func) *real_##func = NULL; \ if (!real_##func) { \ real_##func = dlsym(RTLD_NEXT, #func); \ } /** * 限制网络访问 */ // ------------------ 匹配 域名 黑名单 ------------------ static int match_banned_domain(const char *target, const char *rules) { if (!target || !rules || !*rules) return 0; char *list = strdup(rules); char *token = strtok(list, ","); int matched = 0; while (token) { while (*token == ' ' || *token == '\t') token++; if (*token) { regex_t re; char buf[512]; snprintf(buf, sizeof(buf), "^%s$", token); if (regcomp(&re, buf, REG_EXTENDED | REG_NOSUB | REG_ICASE) == 0) { if (regexec(&re, target, 0, NULL, 0) == 0) matched = 1; regfree(&re); } } if (matched) break; token = strtok(NULL, ","); } free(list); return matched; } // ------------------ 匹配 IP/CIDR 黑名单 ------------------ static int match_banned_ip(const char *ip_str, const char *rules) { if (!ip_str || !rules || !*rules) return 0; struct in_addr ip4; struct in6_addr ip6; int is_v4 = inet_pton(AF_INET, ip_str, &ip4) == 1; int is_v6 = inet_pton(AF_INET6, ip_str, &ip6) == 1; if (!is_v4 && !is_v6) return 0; char *list = strdup(rules); char *token = strtok(list, ","); int blocked = 0; while (token) { while (*token == ' ' || *token == '\t') token++; if (!*token) goto next; char *slash = strchr(token, '/'); int prefix = -1; if (slash) { *slash++ = '\0'; prefix = atoi(slash); } /* ---------- IPv4 ---------- */ if (is_v4) { struct in_addr net4; if (inet_pton(AF_INET, token, &net4) == 1) { if (prefix < 0) { /* 单 IP */ if (ip4.s_addr == net4.s_addr) { blocked = 1; break; } } else if (prefix >= 0 && prefix <= 32) { uint32_t mask = prefix == 0 ? 0 : htonl(0xFFFFFFFFu << (32 - prefix)); if ((ip4.s_addr & mask) == (net4.s_addr & mask)) { blocked = 1; break; } } } } /* ---------- IPv6 ---------- */ if (is_v6) { struct in6_addr net6; if (inet_pton(AF_INET6, token, &net6) == 1) { if (prefix < 0) { /* 单 IP */ if (memcmp(&ip6, &net6, sizeof(ip6)) == 0) { blocked = 1; break; } } else if (prefix >= 0 && prefix <= 128) { int full = prefix / 8; int rem = prefix % 8; if (full && memcmp(ip6.s6_addr, net6.s6_addr, full) != 0) goto next; if (rem) { uint8_t mask = (uint8_t)(0xFF << (8 - rem)); if ((ip6.s6_addr[full] & mask) != (net6.s6_addr[full] & mask)) goto next; } blocked = 1; break; } } } next: token = strtok(NULL, ","); } free(list); return blocked; } // ------------------ 网络拦截 ------------------ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { RESOLVE_REAL(connect); ensure_config_loaded(); if (is_sandbox_user() && addr->sa_family == AF_UNIX) { struct sockaddr_un *un = (struct sockaddr_un *)addr; throw_permission_denied_err(false, "access unix socket: %s", un->sun_path[0] ? un->sun_path : "(abstract)"); return -1; } char ip[INET6_ADDRSTRLEN] = {0}; if (addr->sa_family == AF_INET) { inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, ip, sizeof(ip)); } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { struct in_addr v4; memcpy(&v4, &sin6->sin6_addr.s6_addr[12], sizeof(v4)); inet_ntop(AF_INET, &v4, ip, sizeof(ip)); } else { inet_ntop(AF_INET6, &sin6->sin6_addr, ip, sizeof(ip)); } } if (is_sandbox_user() && match_banned_ip(ip, banned_hosts)) { throw_permission_denied_err(false, "access %s", ip); return -1; } return real_connect(sockfd, addr, addrlen); } int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { RESOLVE_REAL(getaddrinfo); ensure_config_loaded(); if (node && is_sandbox_user()) { struct in_addr ip4; struct in6_addr ip6; int is_ip = inet_pton(AF_INET, node, &ip4) == 1 || inet_pton(AF_INET6, node, &ip6) == 1; if (!is_ip && match_banned_domain(node, banned_hosts)) { throw_permission_denied_err(false, "access %s", node); return EAI_SYSTEM; } } return real_getaddrinfo(node, service, hints, res); } /** * 限制创建子进程 */ static int allow_create_subprocess() { ensure_config_loaded(); return allow_subprocess || !is_sandbox_user(); } static int not_supported(const char *function_name) { fprintf(stderr, "Not supported function: %s\n", function_name); _exit(126); return -1; } int execv(const char *path, char *const argv[]) { RESOLVE_REAL(execv); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_execv(path, argv); } int __execv(const char *path, char *const argv[]) { return execv(path, argv); } int execve(const char *filename, char *const argv[], char *const envp[]) { RESOLVE_REAL(execve); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_execve(filename, argv, envp); } int __execve(const char *filename, char *const argv[], char *const envp[]) { return execve(filename, argv, envp); } int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags) { RESOLVE_REAL(execveat); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_execveat(dirfd, pathname, argv, envp, flags); } int execvpe(const char *file, char *const argv[], char *const envp[]) { RESOLVE_REAL(execvpe); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_execvpe(file, argv, envp); } int __execvpe(const char *file, char *const argv[], char *const envp[]) { return execvpe(file, argv, envp); } int execvp(const char *file, char *const argv[]) { RESOLVE_REAL(execvp); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_execvp(file, argv); } int __execvp(const char *file, char *const argv[]) { return execvp(file, argv); } int execl(const char *path, const char *arg, ...) { return not_supported("execl"); } int __execl(const char *path, const char *arg, ...) { return not_supported("__execl"); } int execlp(const char *file, const char *arg, ...) { return not_supported("execlp"); } int __execlp(const char *file, const char *arg, ...) { return not_supported("__execlp"); } int execle(const char *path, const char *arg, ...) { return not_supported("execle"); } pid_t fork(void) { RESOLVE_REAL(fork); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_fork(); } pid_t __fork(void) { return fork(); } pid_t vfork(void) { RESOLVE_REAL(vfork); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_vfork(); } pid_t __vfork(void) { return vfork(); } int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { RESOLVE_REAL(clone); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); va_list ap; va_start(ap, arg); long a4 = va_arg(ap, long); long a5 = va_arg(ap, long); va_end(ap); return real_clone(fn, child_stack, flags, arg, (void *)a4, (void *)a5); } int clone3(struct clone_args *cl_args, size_t size) { RESOLVE_REAL(clone3); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_clone3(cl_args, size); } int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { RESOLVE_REAL(posix_spawn); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_posix_spawn(pid, path, file_actions, attrp, argv, envp); } int __posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { return posix_spawn(pid, path, file_actions, attrp, argv, envp); } int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { RESOLVE_REAL(posix_spawnp); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } int __posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { return posix_spawnp(pid, file, file_actions, attrp, argv, envp); } FILE *popen(const char *command, const char *type) { RESOLVE_REAL(popen); if (!allow_create_subprocess()) { fprintf(stderr, "Permission denied to create subprocess.\n"); errno = EACCES; return NULL; } return real_popen(command, type); } FILE *__popen(const char *command, const char *type) { return popen(command, type); } int system(const char *command) { RESOLVE_REAL(system); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_system(command); } int __libc_system(const char *command) { RESOLVE_REAL(__libc_system); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real___libc_system(command); } pid_t __libc_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { RESOLVE_REAL(__libc_clone); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); va_list ap; va_start(ap, arg); long a4 = va_arg(ap, long); long a5 = va_arg(ap, long); va_end(ap); return real___libc_clone(fn, child_stack, flags, arg, (void *)a4, (void *)a5); } pid_t forkpty(int *amaster, char *name, const struct termios *termp, const struct winsize *winp) { RESOLVE_REAL(forkpty); if (!allow_create_subprocess()) return throw_permission_denied_err(true, "create subprocess"); return real_forkpty(amaster, name, termp, winp); } pid_t __forkpty(int *amaster, char *name, const struct termios *termp, const struct winsize *winp) { return forkpty(amaster, name, termp, winp); } /** * 限制调用syscall */ static int allow_access_syscall() { ensure_config_loaded(); return allow_syscall || !is_sandbox_user(); } long (*real_syscall)(long, ...) = NULL; long syscall(long number, ...) { RESOLVE_REAL(syscall); va_list ap; va_start(ap, number); long a1 = va_arg(ap, long); long a2 = va_arg(ap, long); long a3 = va_arg(ap, long); long a4 = va_arg(ap, long); long a5 = va_arg(ap, long); long a6 = va_arg(ap, long); va_end(ap); switch (number) { case SYS_execve: case SYS_execveat: #ifdef SYS_fork case SYS_fork: #endif #ifdef SYS_vfork case SYS_vfork: #endif case SYS_clone: case SYS_clone3: #ifdef SYS_posix_spawn case SYS_posix_spawn: #endif #ifdef SYS_posix_spawnp case SYS_posix_spawnp: #endif case SYS_socket: case SYS_connect: case SYS_bind: case SYS_listen: case SYS_accept: case SYS_accept4: case SYS_sendto: case SYS_recvmsg: case SYS_getsockopt: case SYS_setsockopt: case SYS_ptrace: case SYS_setuid: case SYS_setgid: case SYS_reboot: case SYS_mount: #ifdef SYS_chown case SYS_chown: #endif #ifdef SYS_chmod case SYS_chmod: #endif case SYS_fchmodat: case SYS_mprotect: #ifdef SYS_open case SYS_open: #endif case SYS_openat: case SYS_swapon: case SYS_swapoff: case SYS_kill: case SYS_mmap: case SYS_munmap: case SYS_memfd_create: case SYS_shmat: case SYS_shmget: case SYS_shmctl: case SYS_prctl: if (!allow_access_syscall()) { throw_permission_denied_err(true, "access syscall %ld", number); } } return real_syscall(number, a1, a2, a3, a4, a5, a6); } /** * 限制加载动态链接库 */ static int is_in_allow_dl_paths(const char *filename) { if (!filename || !*filename) return 1; ensure_config_loaded(); if (!allow_dl_paths || !*allow_dl_paths) return 0; char real_file[PATH_MAX]; if (!realpath(filename, real_file)) return 0; char *rules = strdup(allow_dl_paths); if (!rules) return 0; int allowed = 0; char *saveptr = NULL; for (char *token = strtok_r(rules, ",", &saveptr); token; token = strtok_r(NULL, ",", &saveptr)) { while (*token == ' ' || *token == '\t') token++; if (!*token) continue; char real_rule[PATH_MAX]; if (!realpath(token, real_rule)) continue; size_t len = strlen(real_rule); if (strncmp(real_file, real_rule, len) == 0 && (real_file[len] == '\0' || real_file[len] == '/')) { allowed = 1; break; } } free(rules); return allowed; } void *dlopen(const char *filename, int flag) { RESOLVE_REAL(dlopen); if (is_sandbox_user() && !is_in_allow_dl_paths(filename)) { throw_permission_denied_err(true, "access file %s", filename); } return real_dlopen(filename, flag); } void *__dlopen(const char *filename, int flag) { return dlopen(filename, flag); } void *dlmopen(Lmid_t lmid, const char *filename, int flags) { RESOLVE_REAL(dlmopen); if (is_sandbox_user() && !is_in_allow_dl_paths(filename)) { throw_permission_denied_err(true, "access file %s", filename); } return real_dlmopen(lmid, filename, flags); } void *__dlmopen(Lmid_t lmid, const char *filename, int flags) { return dlmopen(lmid, filename, flags); } void* mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off) { RESOLVE_REAL(mmap); if (is_sandbox_user() && (prot & PROT_EXEC)) { if ((flags & MAP_ANONYMOUS) || fd < 0) { //匿名映射:直接拒绝 throw_permission_denied_err(true, "mmap(anonymous)"); } char link[64]; snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); char real_path[PATH_MAX]; ssize_t n = readlink(link, real_path, sizeof(real_path) - 1); if (n < 0) { throw_permission_denied_err(true, "mmap(readlink failed)"); } real_path[n] = '\0'; if (!is_in_allow_dl_paths(real_path)) { throw_permission_denied_err(true, "mmap %s", real_path); } } return real_mmap(addr, len, prot, flags, fd, off); } void* mmap64(void *addr, size_t len, int prot, int flags, int fd, off_t off) { return mmap(addr, len, prot, flags, fd, off); } int mprotect(void *addr, size_t len, int prot) { RESOLVE_REAL(mprotect); if (is_sandbox_user() && (prot & PROT_EXEC)) { throw_permission_denied_err(true, "mprotect"); } return real_mprotect(addr, len, prot); } ================================================ FILE: installer/start-all.sh ================================================ #!/bin/bash set -e if [ -f "/opt/maxkb/PG_VERSION" ] || [ -f "/var/lib/postgresql/data/PG_VERSION" ]; then # 如果是v1版本一键安装的的目录则退出 echo -e "\033[1;31mFATAL ERROR: Upgrade from v1 to v2 is not supported.\033[0m" echo -e "\033[1;31mThe process will exit.\033[0m" exit 1 fi if [ "$MAXKB_DB_HOST" = "127.0.0.1" ]; then echo -e "\033[1;32mPostgreSQL starting...\033[0m" /usr/bin/start-postgres.sh & postgres_pid=$! sleep 5 wait-for-it 127.0.0.1:5432 --timeout=120 --strict -- echo -e "\033[1;32mPostgreSQL started.\033[0m" fi if [ "$MAXKB_REDIS_HOST" = "127.0.0.1" ]; then echo -e "\033[1;32mRedis starting...\033[0m" /usr/bin/start-redis.sh & redis_pid=$! sleep 5 wait-for-it 127.0.0.1:6379 --timeout=60 --strict -- echo -e "\033[1;32mRedis started.\033[0m" fi echo -e "\033[1;32mMaxKB starting...\033[0m" /usr/bin/start-maxkb.sh & maxkb_pid=$! sleep 10 wait-for-it 127.0.0.1:8080 --timeout=180 --strict -- echo -e "\033[1;32mMaxKB started.\033[0m" wait -n echo -e "\033[1;31mSystem is shutting down.\033[0m" kill $postgres_pid $redis_pid $maxkb_pid 2>/dev/null wait ================================================ FILE: installer/start-maxkb.sh ================================================ #!/bin/bash if [ ! -d /opt/maxkb/logs ]; then mkdir -p /opt/maxkb/logs fi chmod -R 700 /opt/maxkb/logs if [ ! -d /opt/maxkb/local ]; then mkdir -p /opt/maxkb/local chmod 700 /opt/maxkb/local fi mkdir -p /opt/maxkb/python-packages rm -f /opt/maxkb-app/tmp/* python /opt/maxkb-app/main.py start ================================================ FILE: installer/start-postgres.sh ================================================ #!/bin/bash mkdir -p /opt/maxkb/data/postgresql docker-entrypoint.sh postgres -c max_connections=${POSTGRES_MAX_CONNECTIONS} ================================================ FILE: installer/start-redis.sh ================================================ #!/bin/bash if [ ! -d /opt/maxkb/data/redis ]; then mkdir -p /opt/maxkb/data/redis chmod 700 /opt/maxkb/data/redis fi if [ ! -d /opt/maxkb/logs ]; then mkdir -p /opt/maxkb/logs chmod 700 /opt/maxkb/logs fi if [ ! -f /opt/maxkb/conf/redis.conf ]; then mkdir -p /opt/maxkb/conf touch /opt/maxkb/conf/redis.conf chmod 700 /opt/maxkb/conf/redis.conf cat < /opt/maxkb/conf/redis.conf bind 0.0.0.0 port 6379 databases 16 maxmemory 1G aof-use-rdb-preamble yes save 30 1 save 10 10 save 5 20 dbfilename dump.rdb rdbcompression yes appendonly yes appendfilename "appendonly.aof" appendfsync everysec auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb maxmemory-policy allkeys-lru loglevel warning logfile /opt/maxkb/logs/redis.log dir /opt/maxkb/data/redis requirepass ${REDIS_PASSWORD} EOF fi redis-server /opt/maxkb/conf/redis.conf ================================================ FILE: main.py ================================================ import argparse import logging import os import sys import time import django from django.core import management BASE_DIR = os.path.dirname(os.path.abspath(__file__)) APP_DIR = os.path.join(BASE_DIR, 'apps') os.chdir(BASE_DIR) sys.path.insert(0, APP_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "maxkb.settings") def collect_static(): """ 收集静态文件到指定目录 本项目主要是将前端vue/dist的前端项目放到静态目录下面 :return: """ logging.info("Collect static files") try: management.call_command('collectstatic', '--no-input', '-c', verbosity=0, interactive=False) logging.info("Collect static files done") except: pass def perform_db_migrate(): """ 初始化数据库表 """ logging.info("Check database structure change ...") logging.info("Migrate model change to database ...") try: management.call_command('migrate') except Exception as e: logging.error('Perform migrate failed, exit', exc_info=True) sys.exit(11) def start_services(): services = args.services if isinstance(args.services, list) else [args.services] start_args = [] if args.daemon: start_args.append('--daemon') if args.force: start_args.append('--force') if args.worker: start_args.extend(['--worker', str(args.worker)]) else: worker = os.environ.get('MAXKB_CORE_WORKER') if isinstance(worker, str) and worker.isdigit(): start_args.extend(['--worker', worker]) try: management.call_command(action, *services, *start_args) except KeyboardInterrupt: logging.info('Cancel ...') time.sleep(2) except Exception as exc: logging.error("Start service error {}: {}".format(services, exc)) time.sleep(2) def dev(): services = args.services if isinstance(args.services, list) else args.services if services.__contains__('web'): management.call_command('runserver', "0.0.0.0:8080") elif services.__contains__('celery'): management.call_command('celery', 'celery') elif services.__contains__('local_model'): from maxkb.const import CONFIG bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}' management.call_command('runserver', bind) if __name__ == '__main__': os.environ['HF_HOME'] = '/opt/maxkb-app/model/base' os.environ['TMPDIR'] = '/opt/maxkb-app/tmp' parser = argparse.ArgumentParser( description=""" qabot service control tools; Example: \r\n %(prog)s start all -d; """ ) parser.add_argument( 'action', type=str, choices=("start", "dev", "upgrade_db", "collect_static"), help="Action to run" ) args, e = parser.parse_known_args() parser.add_argument( "services", type=str, default='all' if args.action == 'start' else 'web', nargs="*", choices=("all", "web", "task") if args.action == 'start' else ("web", "celery", 'local_model'), help="The service to start", ) parser.add_argument('-d', '--daemon', nargs="?", const=True) parser.add_argument('-w', '--worker', type=int, nargs="?") parser.add_argument('-f', '--force', nargs="?", const=True) args = parser.parse_args() action = args.action services = args.services if isinstance(args.services, list) else args.services if services.__contains__('web'): os.environ.setdefault('SERVER_NAME', 'web') elif services.__contains__('local_model'): os.environ.setdefault('SERVER_NAME', 'local_model') django.setup() if action == "upgrade_db": perform_db_migrate() elif action == "collect_static": collect_static() elif action == 'dev': collect_static() perform_db_migrate() dev() else: collect_static() perform_db_migrate() start_services() ================================================ FILE: pyproject.toml ================================================ [project] name = "maxkb" version = "2.0.0" description = "强大易用的开源企业级智能体平台" authors = [{ name = "shaohuzhang1", email = "shaohu.zhang@fit2cloud.com" }] requires-python = "~=3.11.0" readme = "README.md" dependencies = [ "django==5.2.12", "drf-spectacular[sidecar]==0.28.0", "django-redis==6.0.0", "django-db-connection-pool==1.2.6", "django-mptt==0.17.0", "psycopg[binary]==3.2.9", "python-dotenv==1.1.1", "uuid-utils==0.14.0", "captcha==0.7.1", "pytz==2025.2", "psutil==7.0.0", "cffi==1.17.1", "beautifulsoup4==4.13.4", "jieba==0.42.1", "langchain==1.2.12", "langchain-openai==1.1.11", "langchain-anthropic==1.3.4", "langchain-community==0.4.1", "langchain-deepseek==1.0.1", "langchain-google-genai==4.2.1", "langchain-mcp-adapters==0.2.1", "langchain-huggingface==1.2.1", "langchain-ollama==1.0.1", "langchain-aws==1.4.0", "langgraph==1.1.1", "deepagents==0.4.10", "torch==2.8.0", "numpy==1.26.4", "sentence-transformers==5.0.0", "qianfan==0.4.12.3", "zhipuai==2.1.5.20250708", "volcengine-python-sdk[ark]==4.0.5", "boto3==1.42.46", "tencentcloud-sdk-python==3.0.1420", "xinference-client==1.7.1.post1", "anthropic==0.79.0", "dashscope==1.25.7", "celery[sqlalchemy]==5.5.3", "django-celery-beat==2.8.1", "celery-once==3.0.1", "django-apscheduler==0.7.0", "html2text==2025.4.15", "openpyxl==3.1.5", "python-docx==1.2.0", "xlrd==2.0.2", "xlwt==1.3.0", "pymupdf==1.26.3", "pypdf==6.9.1", "pydub==0.25.1", "pysilk==0.0.1", "gunicorn==23.0.0", "python-daemon==3.1.2", "websockets==15.0.1", "pylint==3.3.7", "cohere==5.17.0", "jsonpath-ng==1.7.0", ] [tool.uv] package = false [[tool.uv.index]] name = "pytorch" url = "https://download.pytorch.org/whl/cpu" explicit = true [[tool.uv.index]] name = "macpytorch" url = "https://download.pytorch.org/whl" explicit = true [tool.uv.sources] torch = [ { index = "pytorch", marker = "sys_platform == 'linux'" }, { index = "pytorch", marker = "sys_platform == 'win'" }, { index = "macpytorch", marker = "sys_platform == 'darwin'" }, ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" ================================================ FILE: ui/.editorconfig ================================================ [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] charset = utf-8 indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf max_line_length = 100 ================================================ FILE: ui/.gitattributes ================================================ * text=auto eol=lf ================================================ FILE: ui/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules .DS_Store dist dist-ssr coverage *.local /cypress/videos/ /cypress/screenshots/ # Editor directories and files .vscode/* !.vscode/extensions.json .idea *.suo *.ntvs* *.njsproj *.sln *.sw? *.tsbuildinfo ================================================ FILE: ui/.prettierrc.json ================================================ { "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "singleQuote": true, "printWidth": 100 } ================================================ FILE: ui/README.md ================================================ # ui This template should help get you started developing with Vue 3 in Vite. ## Recommended IDE Setup [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). ## Type Support for `.vue` Imports in TS TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. ## Customize configuration See [Vite Configuration Reference](https://vite.dev/config/). ## Project Setup ```sh npm install ``` ### Compile and Hot-Reload for Development ```sh npm run dev ``` ### Type-Check, Compile and Minify for Production ```sh npm run build ``` ### Lint with [ESLint](https://eslint.org/) ```sh npm run lint ``` ================================================ FILE: ui/admin.html ================================================ %VITE_APP_TITLE%
================================================ FILE: ui/chat.html ================================================ %VITE_APP_TITLE%
================================================ FILE: ui/env.d.ts ================================================ /// declare module 'katex' interface Window { sendMessage: ?((message: string, other_params_data: any) => void) chatUserProfile: ?(() => any) MaxKB: { prefix: string chatPrefix: string } } ================================================ FILE: ui/eslint.config.ts ================================================ import { globalIgnores } from 'eslint/config' import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' import pluginVue from 'eslint-plugin-vue' import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' // To allow more languages other than `ts` in `.vue` files, uncomment the following lines: // import { configureVueProject } from '@vue/eslint-config-typescript' // configureVueProject({ scriptLangs: ['ts', 'tsx'] }) // More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup export default defineConfigWithVueTs( { name: 'app/files-to-lint', files: ['**/*.{ts,mts,tsx,vue}'], }, globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), pluginVue.configs['flat/essential'], vueTsConfigs.recommended, skipFormatting, { rules: { 'vue/multi-word-component-names': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': ['off'], } } ) ================================================ FILE: ui/package.json ================================================ { "name": "ui", "version": "0.0.0", "private": true, "type": "module", "scripts": { "dev": "vite", "chat": "vite --mode chat", "build": "run-p type-check \"build-only {@}\" --", "build-chat": "run-p type-check \"build-only-chat {@}\" --", "preview": "vite preview", "build-only": "vite build", "build-only-chat": "vite build --mode chat", "type-check": "vue-tsc --build", "lint": "eslint . --fix", "format": "prettier --write src/" }, "dependencies": { "@antv/layout": "^0.3.1", "@codemirror/lang-json": "^6.0.1", "@codemirror/lang-python": "^6.2.1", "@codemirror/theme-one-dark": "^6.1.2", "@logicflow/core": "^1.2.27", "@logicflow/extension": "^1.2.27", "@vavt/cm-extension": "^1.9.1", "@vueuse/core": "^13.3.0", "axios": "^1.8.4", "cron-validator": "^1.4.0", "cropperjs": "^1.6.2", "dingtalk-jsapi": "^3.1.0", "echarts": "^5.6.0", "el-table-infinite-scroll": "^3.0.8", "element-plus": "^2.13.5", "file-saver": "^2.0.5", "highlight.js": "^11.11.1", "html-to-image": "^1.11.13", "html2canvas": "^1.4.1", "jspdf": "^4.1.0", "katex": "^0.16.10", "marked": "^12.0.2", "md-editor-v3": "^5.8.2", "mermaid": "^11.12.0", "moment": "^2.30.1", "nanoid": "^5.1.5", "node-forge": "^1.3.1", "nprogress": "^0.2.0", "pinia": "^3.0.1", "recorder-core": "^1.3.25011100", "sanitize-html": "^2.17.0", "screenfull": "^6.0.2", "sortablejs": "^1.15.6", "svg2pdf.js": "^2.5.0", "use-element-plus-theme": "^0.0.5", "vite-plugin-html": "^3.2.2", "vue": "^3.5.13", "vue-clipboard3": "^2.0.0", "vue-codemirror": "^6.1.1", "vue-demi": "^0.14.10", "vue-draggable-plus": "^0.6.0", "vue-i18n": "^11.1.3", "vue-router": "^4.5.0", "vue3-menus": "^1.1.2" }, "devDependencies": { "@tsconfig/node22": "^22.0.1", "@types/crypto-js": "^4.2.2", "@types/file-saver": "^2.0.7", "@types/node": "^22.14.0", "@types/node-forge": "^1.3.14", "@types/nprogress": "^0.2.3", "@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue-jsx": "^4.1.2", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.5.0", "@vue/tsconfig": "^0.7.0", "eslint": "^9.22.0", "eslint-plugin-vue": "~10.0.0", "jiti": "^2.4.2", "npm-run-all2": "^7.0.2", "prettier": "3.5.3", "sass": "^1.86.3", "sass-loader": "^16.0.5", "typescript": "~5.8.0", "unplugin-vue-define-options": "^3.0.0-beta.8", "vite": "^6.2.4", "vite-plugin-vue-devtools": "^7.7.2", "vue-tsc": "^2.2.8" } } ================================================ FILE: ui/public/tool/bochaai/detail.md ================================================ ## 概述 博查工具是一个支持自然语言搜索的 Web Search API,从近百亿网页和生态内容源中搜索高质量世界知识,包括新闻、图片、视频、百科、机酒、学术等。 ## 配置 1. 获取API Key  在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。 ![API Key](/admin/tool/img/bocha_APIKey.jpg) 2. 在函数库中配置 在函数库的博查函数面板中,点击 … > 启动参数,填写 API 密钥,并启用该函数。 ![启动参数](/admin/tool/img/bocha_setting.jpg) 3. 在应用中使用 在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。 ![应用中使用](/admin/tool/img/bocha_app_used.jpg) ================================================ FILE: ui/public/tool/google_search/detail.md ================================================ ## 概述 Google 搜索工具是一个实时 API,可提取搜索引擎结果,提供来自 Google 的结构化数据。它支持各种搜索类型,包括 Web、图像、新闻和地图。 ## 配置 1. 创建 Google Custom Search Engine 在[Programmable Search Engine](https://programmablesearchengine.google.com/)中 添加 Search Engine ![google 创建引擎](/admin/tool/img/google_AddSearchEngine.jpg) 2. 获取cx参数 进入添加的引擎详情中,在【基本】菜单中获取搜索引擎的ID,即cx。 ![google cx ](/admin/tool/img/google_cx.jpg) 3. 获取 API Key 打开 https://developers.google.com/custom-search/v1/overview?hl=zh-cn 获取API Key。 ![google API Key](/admin/tool/img/google_APIKey.jpg) 4. 配置启动参数 在Google 搜索函数的启动参数中填写配置以上参数,并启用该函数。 ![启动参数](/admin/tool/img/google_setting.jpg) 5. 在应用中使用 在高级编排应用中,点击添加组件->函数库->Google搜索,设置使用参数。 ![应用中使用](/admin/tool/img/google_app_used.jpg) ================================================ FILE: ui/public/tool/langsearch/detail.md ================================================ ## 概述 LangSearch 是一个提供免费Web Search API和Rerank API的服务,支持新闻、图像、视频等内容。它结合了关键词和向量进行混合搜索,以提高准确性。 ## 配置 1. 获取API Key  在[LangSearch](https://langsearch.com/overview) 上申请 API 密钥。 ![API Key](/admin/tool/img/langsearch_APIKey.jpg) 2. 在函数库中配置 在函数库的LangSearch函数面板中,点击 … > 启动参数,填写 API 密钥,并启用该函数。 ![启动参数](/admin/tool/img/langsearch_setting.jpg) 3. 在应用中使用 在高级编排应用中,点击添加组件->函数库->LangSearch,设置使用参数。 ![应用中使用](/admin/tool/img/langsearch_app_used.jpg) ================================================ FILE: ui/public/tool/mysql/detail.md ================================================ ## 概述 MySQL查询是一个连接MySQL数据库执行SQL查询的工具。 ## 配置   1. 在函数库中配置启动参数 在函数库的MySQL函数面板中,点击 … > 启动参数,填写数据库连接参数,并启用该函数。 ![启动参数](/admin/tool/img/MySQL_setting.jpg) 2. 在应用中使用 在高级编排应用中,点击添加组件->函数库->MySQL查询,设置查询内容。 ![应用中使用](/admin/tool/img/MySQL_app_used.jpg) ================================================ FILE: ui/public/tool/postgresql/detail.md ================================================ ## 概述 PostgreSQL查询是一个连接PostgreSQL数据库执行SQL查询的工具。 ## 配置   1. 在函数库中配置启动参数 在函数库的PostgreSQL函数面板中,点击 … > 启动参数,填写数据库连接参数,并启用该函数。 ![启动参数](/admin/tool/img/PostgreSQL_setting.jpg) 2. 在应用中使用 在高级编排应用中,点击添加组件->函数库->PostgreSQL查询,设置查询内容。 ![应用中使用](/admin/tool/img/PostgreSQL_app_used.jpg) ================================================ FILE: ui/src/App.vue ================================================ ================================================ FILE: ui/src/api/application/application-key.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put} from '@/request/index' import useStore from '@/stores' import {type Ref} from 'vue' const prefix: any = {_value: '/workspace/'} Object.defineProperty(prefix, 'value', { get: function () { const {user} = useStore() return this._value + user.getWorkspaceId() + '/application' }, }) /** * API_KEY列表 * @param 参数 application_id */ const getAPIKey: (application_id: string, current_page: number, page_size: number, params: any, loading?: Ref) => Promise> = ( application_id, current_page, page_size, params, loading, ) => { return get(`${prefix.value}/${application_id}/application_key/${current_page}/${page_size}`, params, loading) } /** * 新增API_KEY * @param 参数 application_id */ const postAPIKey: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return post(`${prefix.value}/${application_id}/application_key`, {}, undefined, loading) } /** * 删除API_KEY * @param 参数 application_id api_key_id */ const delAPIKey: ( application_id: string, api_key_id: string, loading?: Ref, ) => Promise> = (application_id, api_key_id, loading) => { return del( `${prefix.value}/${application_id}/application_key/${api_key_id}`, undefined, undefined, loading, ) } /** * 修改API_KEY * @param 参数 application_id,api_key_id * data { * is_active: boolean * } */ const putAPIKey: ( application_id: string, api_key_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, api_key_id, data, loading) => { return put( `${prefix.value}/${application_id}/application_key/${api_key_id}`, data, undefined, loading, ) } export default { getAPIKey, postAPIKey, delAPIKey, putAPIKey, } ================================================ FILE: ui/src/api/application/application.ts ================================================ import { Result } from '@/request/Result' import { get, post, postStream, del, put, request, download, exportFile } from '@/request/index' import type { pageRequest } from '@/api/type/common' import type { ApplicationFormType } from '@/api/type/application' import { type Ref } from 'vue' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/application' }, }) /** * 获取全部应用 * @param param * @param loading */ const getAllApplication: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get(`${prefix.value}`, param, loading) } /** * 获取分页应用 * param { "name": "string", } */ const getApplication: ( page: pageRequest, param: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading) } /** * 创建应用 * @param data * @param loading */ const postApplication: ( data: ApplicationFormType, loading?: Ref, ) => Promise> = (data, loading) => { return post(`${prefix.value}`, data, undefined, loading) } /** * 修改应用 * @param application_id * @param data * @param loading */ const putApplication: ( application_id: string, data: ApplicationFormType, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix.value}/${application_id}`, data, undefined, loading) } /** * 移动应用 * @param application_id * @param folder_id * @param loading * @returns */ const moveApplication: ( application_id: string, folder_id: string, loading?: Ref, ) => Promise> = (application_id, folder_id, loading) => { return put(`${prefix.value}/${application_id}/move/${folder_id}`, {}, undefined, loading) } /** * 删除应用 * @param application_id * @param loading */ const delApplication: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return del(`${prefix.value}/${application_id}`, undefined, {}, loading) } /** * 应用详情 * @param application_id * @param loading */ const getApplicationDetail: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix.value}/${application_id}`, undefined, loading) } /** * 获取AccessToken * @param application_id * @param loading */ const getAccessToken: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return get(`${prefix.value}/${application_id}/access_token`, undefined, loading) } /** * 获取应用设置 * @param application_id 应用id * @param loading 加载器 * @returns */ const getApplicationSetting: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix.value}/${application_id}/setting`, undefined, loading) } /** * 修改AccessToken * data { * "is_active": true * } * @param application_id * @param data * @param loading */ const putAccessToken: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix.value}/${application_id}/access_token`, data, undefined, loading) } /** * 替换社区版-修改AccessToken * data { * "show_source": boolean, * "show_history": boolean, * "draggable": boolean, * "show_guide": boolean, * "avatar": file, * "float_icon": file, * } * @param application_id * @param data * @param loading */ const putXpackAccessToken: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix.value}/${application_id}/setting`, data, undefined, loading) } /** * 导出应用 */ const exportApplication = ( application_id: string, application_name: string, loading?: Ref, ) => { return exportFile( application_name + '.mk', `${prefix.value}/${application_id}/export`, undefined, loading, ) } /** * 导入应用 */ const importApplication: ( folder_id: string, data: any, loading?: Ref, ) => Promise> = (folder_id, data, loading) => { return post(`${prefix.value}/folder/${folder_id}/import`, data, undefined, loading) } /** * 统计 * @param application_id * @param data * @param loading */ const getStatistics: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix.value}/${application_id}/application_stats`, data, loading) } /** * 统计token消耗 */ const getTokenUsage: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix.value}/${application_id}/application_token_usage`, data, loading) } /** * 统计提问次数 */ const topQuestions: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix.value}/${application_id}/top_questions`, data, loading) } /** * 打开调试对话id * @param application_id 应用id * @param loading 加载器 * @returns */ const open: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return get(`${prefix.value}/${application_id}/open`, {}, loading) } /** * 生成提示词 * @param workspace_id * @param model_id * @param application_id * @param data * @returns */ const generate_prompt: ( workspace_id: string, model_id: string, application_id: string, data: any, ) => Promise = (workspace_id, model_id, application_id, data) => { const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream( `${prefix}/workspace/${workspace_id}/application/${application_id}/model/${model_id}/prompt_generate`, data, ) } /** * 对话 * chat_id: string * data * @param chat_id * @param data */ const chat: (chat_id: string, data: any) => Promise = (chat_id, data) => { const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream(`${prefix}/chat_message/${chat_id}`, data) } /** * 获取对话用户认证类型 * @param loading 加载器 * @returns */ const getChatUserAuthType: (loading?: Ref) => Promise = (loading) => { return get(`/chat_user/auth/types`, {}, loading) } /** * 获取平台状态 */ const getPlatformStatus: (application_id: string) => Promise> = (application_id) => { return get(`${prefix.value}/${application_id}/platform/status`) } /** * 更新平台状态 */ const updatePlatformStatus: (application_id: string, data: any) => Promise> = ( application_id, data, ) => { return post(`${prefix.value}/${application_id}/platform/status`, data) } /** * 获取平台配置 */ const getPlatformConfig: (application_id: string, type: string) => Promise> = ( application_id, type, ) => { return get(`${prefix.value}/${application_id}/platform/${type}`) } /** * 更新平台配置 */ const updatePlatformConfig: ( application_id: string, type: string, data: any, loading?: Ref, ) => Promise> = (application_id, type, data, loading) => { return post(`${prefix.value}/${application_id}/platform/${type}`, data, undefined, loading) } /** * 应用发布 * @param application_id * @param data * @param loading * @returns */ const publish: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix.value}/${application_id}/publish`, data, {}, loading) } /** * * @param application_id * @param data * @param loading * @returns */ const playDemoText: (application_id: string, data: any, loading?: Ref) => Promise = ( application_id, data, loading, ) => { return download( `${prefix.value}/${application_id}/play_demo_text`, 'post', data, undefined, loading, ) } /** * 文本转语音 */ const postTextToSpeech: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return download( `${prefix.value}/${application_id}/text_to_speech`, 'post', data, undefined, loading, ) } /** * 语音转文本 */ const speechToText: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return post(`${prefix.value}/${application_id}/speech_to_text`, data, undefined, loading) } /** * mcp 节点 */ const getMcpTools: ( application_id: string, mcp_servers: any, loading?: Ref, ) => Promise> = (application_id, mcp_servers, loading) => { return post(`${prefix.value}/${application_id}/mcp_tools`, { mcp_servers }, {}, loading) } /** * 上传文件 * @param file * @param sourceId * @param resourceType * @param loading */ const postUploadFile: ( file: any, sourceId: string, resourceType: | 'KNOWLEDGE' | 'APPLICATION' | 'TOOL' | 'DOCUMENT' | 'CHAT' | 'TEMPORARY_30_MINUTE' | 'TEMPORARY_120_MINUTE' | 'TEMPORARY_1_DAY', loading?: Ref, ) => Promise> = (file, sourceId, resourceType, loading) => { const fd = new FormData() fd.append('file', file) fd.append('source_id', sourceId) fd.append('source_type', resourceType) return post(`/oss/file`, fd, undefined, loading) } const getFile: (application_id: string, params: any) => Promise> = ( application_id, params, ) => { return get(`/oss/get_url/${application_id}`, params) } export default { getAllApplication, getApplication, postApplication, putApplication, delApplication, getApplicationDetail, getAccessToken, putAccessToken, putXpackAccessToken, exportApplication, importApplication, getStatistics, open, chat, getChatUserAuthType, getApplicationSetting, getPlatformStatus, updatePlatformStatus, getPlatformConfig, publish, updatePlatformConfig, playDemoText, postTextToSpeech, speechToText, getMcpTools, postUploadFile, generate_prompt, getTokenUsage, topQuestions, getFile, moveApplication, } ================================================ FILE: ui/src/api/application/chat-log.ts ================================================ import {Result} from '@/request/Result' import { get, post, exportExcelPost, del, put, } from '@/request/index' import type {pageRequest} from '@/api/type/common' import {type Ref} from 'vue' import useStore from '@/stores' const prefix: any = {_value: '/workspace/'} Object.defineProperty(prefix, 'value', { get: function () { const {user} = useStore() return this._value + user.getWorkspaceId() + '/application' }, }) /** * 对话记录提交至知识库 * @param data * @param loading * @param application_id * @param knowledge_id */ const postChatLogAddKnowledge: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return post(`${prefix.value}/${application_id}/add_knowledge`, data, undefined, loading) } /** * 对话日志 * @param 参数 * application_id * param { "start_time": "string", "end_time": "string", } */ const getChatLog: ( application_id: String, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (application_id, page, param, loading) => { return get( `${prefix.value}/${application_id}/chat/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 获得对话日志记录 * @param 参数 * application_id, chart_id,order_asc */ const getChatRecordLog: ( application_id: String, chart_id: String, page: pageRequest, loading?: Ref, order_asc?: boolean, ) => Promise> = (application_id, chart_id, page, loading, order_asc) => { return get( `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${page.current_page}/${page.page_size}`, {order_asc: order_asc !== undefined ? order_asc : true}, loading, ) } /** * 获取标注段落列表信息 * @param 参数 * application_id, chart_id, chart_record_id */ const getMarkChatRecord: ( application_id: string, chart_id: string, chart_record_id: string, loading?: Ref, ) => Promise> = ( application_id, chart_id, chart_record_id, loading, ) => { return get( `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/improve`, undefined, loading, ) } /** * 修改日志记录内容 * @param 参数 * application_id, chart_id, chart_record_id, knowledge_id, document_id * data { "title": "string", "content": "string", "problem_text": "string" } */ const putChatRecordLog: ( application_id: String, chart_id: String, chart_record_id: String, knowledge_id: String, document_id: String, data: any, loading?: Ref, ) => Promise> = ( application_id, chart_id, chart_record_id, knowledge_id, document_id, data, loading, ) => { return put( `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/improve`, data, undefined, loading, ) } /** * 删除标注 * @param 参数 * application_id, chart_id, chart_record_id, knowledge_id, document_id,paragraph_id */ const delMarkChatRecord: ( application_id: String, chart_id: String, chart_record_id: String, knowledge_id: String, document_id: String, paragraph_id: String, loading?: Ref, ) => Promise> = ( application_id, chart_id, chart_record_id, knowledge_id, document_id, paragraph_id, loading, ) => { return del( `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/improve`, undefined, {}, loading, ) } /** * 导出对话日志 * @param 参数 * application_id * param { "start_time": "string", "end_time": "string", } */ const postExportChatLog: ( application_id: string, application_name: string, param: any, data: any, loading?: Ref, ) => void = (application_id, application_name, param, data, loading) => { exportExcelPost( application_name + '.xlsx', `${prefix.value}/${application_id}/chat/export`, param, data, loading, ) } const getChatRecordDetails: ( application_id: string, chat_id: string, chat_record_id: string, loading?: Ref, ) => Promise = (application_id, chat_id, chat_record_id, loading) => { return get( `${prefix.value}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}`, {}, loading, ) } export default { postChatLogAddKnowledge, getChatLog, getChatRecordLog, getMarkChatRecord, putChatRecordLog, delMarkChatRecord, postExportChatLog, getChatRecordDetails, } ================================================ FILE: ui/src/api/application/workflow-version.ts ================================================ import { Result } from '@/request/Result' import { get, put } from '@/request/index' import { type Ref } from 'vue' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/application' }, }) /** * workflow历史版本 */ const getWorkFlowVersion: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix.value}/${application_id}/application_version`, undefined, loading) } /** * workflow历史版本详情 */ const getWorkFlowVersionDetail: ( application_id: string, application_version_id: string, loading?: Ref, ) => Promise> = (application_id, application_version_id, loading) => { return get( `${prefix.value}/${application_id}/application_version/${application_version_id}`, undefined, loading, ) } /** * 修改workflow历史版本 */ const putWorkFlowVersion: ( application_id: string, application_version_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, application_version_id, data, loading) => { return put( `${prefix.value}/${application_id}/application_version/${application_version_id}`, data, undefined, loading, ) } export default { getWorkFlowVersion, getWorkFlowVersionDetail, putWorkFlowVersion, } ================================================ FILE: ui/src/api/chat/chat.ts ================================================ import { Result } from '@/request/Result' import { get, post, postStream, del, put, request, download, exportFile, } from '@/request/chat/index' import { type ChatProfile } from '@/api/type/chat' import { type Ref } from 'vue' import type { ResetPasswordRequest } from '@/api/type/user.ts' import useStore from '@/stores' import type { LoginRequest } from '@/api/type/user' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/application' }, }) /** * 打开调试对话id * @param application_id 应用id * @param loading 加载器 * @returns */ const open: (loading?: Ref) => Promise> = (loading) => { return get('/open', {}, loading) } /** * 对话 * @param 参数 * chat_id: string * data */ const chat: (chat_id: string, data: any) => Promise = (chat_id, data) => { const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/chat') + '/api' return postStream(`${prefix}/chat_message/${chat_id}`, data) } /** * 应用认证信息 */ const chatProfile: (assessToken: string, loading?: Ref) => Promise> = ( assessToken, loading, ) => { return get('/profile', { access_token: assessToken }, loading) } /** * 匿名认证 * @param assessToken * @param loading * @returns */ const anonymousAuthentication: ( assessToken: string, loading?: Ref, ) => Promise> = (assessToken, loading) => { return post('/auth/anonymous', { access_token: assessToken }, {}, loading) } /** * 密码认证 * @param assessToken * @param password * @param loading * @returns */ const passwordAuthentication: ( assessToken: string, password: string, loading?: Ref, ) => Promise> = (assessToken, password, loading) => { return post('auth/password', { access_token: assessToken, password: password }, {}, loading) } /** * 获取应用相关信息 * @param loading * @returns */ const applicationProfile: (loading?: Ref) => Promise> = (loading) => { return get('/application/profile', {}, loading) } /** * 登录 * @param request 登录接口请求表单 * @param loading 接口加载器 * @returns 认证数据 */ const login: ( accessToken: string, request: LoginRequest, loading?: Ref, ) => Promise> = (accessToken: string, request, loading) => { return post('/auth/login/' + accessToken, request, undefined, loading) } const ldapLogin: ( accessToken: string, request: LoginRequest, loading?: Ref, ) => Promise> = (accessToken: string, request, loading) => { return post('/auth/ldap/login/' + accessToken, request, undefined, loading) } /** * 获取验证码 * @param username * @param loading 接口加载器 */ const getCaptcha: ( username?: string, accessToken?: string, loading?: Ref, ) => Promise> = (username, accessToken, loading) => { return get('/captcha', { username: username, accessToken: accessToken }, loading) } /** * 获取二维码类型 */ const getQrType: (loading?: Ref) => Promise> = (loading) => { return get('auth/qr_type', undefined, loading) } const getQrSource: (loading?: Ref) => Promise> = (loading) => { return get('auth/qr_type/source', undefined, loading) } const getDingCallback: ( code: string, accessToken: string, loading?: Ref, ) => Promise> = (code, accessToken, loading) => { return get('auth/dingtalk', { code, accessToken: accessToken }, loading) } const getDingOauth2Callback: ( code: string, accessToken: string, loading?: Ref, ) => Promise> = (code, accessToken, loading) => { return get('auth/dingtalk/oauth2', { code, accessToken: accessToken }, loading) } const getWecomCallback: ( code: string, accessToken: string, loading?: Ref, ) => Promise> = (code, accessToken, loading) => { return get('auth/wecom', { code, accessToken: accessToken }, loading) } const getLarkCallback: ( code: string, accessToken: string, loading?: Ref, ) => Promise> = (code, accessToken, loading) => { return get('auth/lark/oauth2', { code, accessToken: accessToken }, loading) } /** * 获取认证设置 */ const getAuthSetting: (auth_type: string, loading?: Ref) => Promise> = ( auth_type, loading, ) => { return get(`/chat_user/${auth_type}/detail`, undefined, loading) } /** * 点赞点踩 * @param chat_id 对话id * @param chat_record_id 对话记录id * @param vote_status 点赞状态 * @param loading 加载器 * @returns */ const vote: ( chat_id: string, chat_record_id: string, vote_status: string, vote_reason?: string, vote_other_content?: string, loading?: Ref, ) => Promise> = ( chat_id, chat_record_id, vote_status, vote_reason, vote_other_content, loading, ) => { const data = { vote_status, ...(vote_reason !== undefined && { vote_reason }), ...(vote_other_content !== undefined && { vote_other_content }), } return put(`/vote/chat/${chat_id}/chat_record/${chat_record_id}`, data, undefined, loading) } const pageChat: ( current_page: number, page_size: number, loading?: Ref, ) => Promise> = (current_page, page_size, loading) => { return get(`/historical_conversation/${current_page}/${page_size}`, undefined, loading) } const pageChatRecord: ( chat_id: string, current_page: number, page_size: number, loading?: Ref, ) => Promise> = (chat_id, current_page, page_size, loading) => { return get( `/historical_conversation_record/${chat_id}/${current_page}/${page_size}`, undefined, loading, ) } /** * 登出 */ const logout: (loading?: Ref) => Promise> = (loading) => { return post('/auth/logout', undefined, undefined, loading) } /** * 重置密码 */ const resetCurrentPassword: ( request: ResetPasswordRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post('/chat_user/current/reset_password', request, undefined, loading) } /** * 获取当前用户信息 */ const getChatUserProfile: (loading?: Ref) => Promise> = (loading) => { return get('/chat_user/profile', {}, loading) } /** * 获取对话详情 * @param chat_id 对话id * @param chat_record_id 对话记录id * @param loading 加载器 * @returns */ const getChatRecord: ( chat_id: string, chat_record_id: string, loading?: Ref, ) => Promise> = (chat_id, chat_record_id, loading) => { return get(`historical_conversation/${chat_id}/record/${chat_record_id}`, {}, loading) } /** * 文本转语音 */ const textToSpeech: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return download(`text_to_speech`, 'post', data, undefined, loading) } /** * 语音转文本 */ const speechToText: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`speech_to_text`, data, undefined, loading) } /** * * @param chat_id 对话ID * @param loading * @returns */ const deleteChat: (chat_id: string, loading?: Ref) => Promise> = ( chat_id, loading, ) => { return del(`historical_conversation/${chat_id}`, undefined, undefined, loading) } /** * * @param loading * @returns */ const clearChat: (loading?: Ref) => Promise> = (loading) => { return del(`historical_conversation/clear`, undefined, undefined, loading) } /** * * @param chat_id 对话id * @param data 对话简介 * @param loading * @returns */ const modifyChat: (chat_id: string, data: any, loading?: Ref) => Promise> = ( chat_id, data, loading, ) => { return put(`historical_conversation/${chat_id}`, data, undefined, loading) } /** * 上传文件 * @param file 文件 * @param sourceId 资源id * @param resourceType 资源类型 * @returns */ const postUploadFile: ( file: any, sourceId: string, resourceType: | 'KNOWLEDGE' | 'APPLICATION' | 'TOOL' | 'DOCUMENT' | 'CHAT' | 'TEMPORARY_30_MINUTE' | 'TEMPORARY_120_MINUTE' | 'TEMPORARY_1_DAY', loading?: Ref, ) => Promise> = (file, sourceId, sourceType, loading) => { const fd = new FormData() fd.append('file', file) fd.append('source_id', sourceId) fd.append('source_type', sourceType) return post(`/oss/file`, fd, undefined, loading) } const getFile: (application_id: string, params: any) => Promise> = ( application_id, params, ) => { return get(`/oss/get_url/${application_id}`, params) } /** * 生成分享链接 * @param 参数 * chat_id: string * data */ const postShareChat: ( application_id: string, chat_id: string, data: any, loading?: Ref, ) => Promise = (application_id, chat_id, data, loading) => { return post(`/${application_id}/chat/${chat_id}/share_chat`, data, undefined, loading) } const getShareLink: (link: string) => Promise> = (link) => { return get(`/share/${link}`, undefined) } export default { open, chat, chatProfile, anonymousAuthentication, applicationProfile, login, getCaptcha, getDingCallback, getQrType, getWecomCallback, getDingOauth2Callback, getLarkCallback, getQrSource, ldapLogin, getAuthSetting, passwordAuthentication, vote, pageChat, pageChatRecord, logout, resetCurrentPassword, getChatUserProfile, getChatRecord, textToSpeech, speechToText, deleteChat, clearChat, modifyChat, postUploadFile, getFile, postShareChat, getShareLink, } ================================================ FILE: ui/src/api/chat-user/auth-setting.ts ================================================ import {Result} from '@/request/Result' import {get, post, put} from '@/request/index' import {type Ref} from 'vue' const prefix = '/chat_user/auth' /** * 获取认证设置 */ const getAuthSetting: (auth_type: string, loading?: Ref) => Promise> = (auth_type, loading) => { return get(`${prefix}/${auth_type}/detail`, undefined, loading) } /** * ldap连接测试 */ const postAuthSetting: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}/connection`, data, undefined, loading) } /** * 修改邮箱设置 */ const putAuthSetting: (auth_type: string, data: any, loading?: Ref) => Promise> = ( auth_type, data, loading ) => { return put(`${prefix}/${auth_type}/info`, data, undefined, loading) } const platformPrefix = '/chat_user/auth/platform' const getPlatformInfo: (loading?: Ref) => Promise> = (loading) => { return get(`${platformPrefix}/source`, undefined, loading) } const updateConfig: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${platformPrefix}/source`, data, undefined, loading) } const validateConnection: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${platformPrefix}/source`, data, undefined, loading) } export default { getAuthSetting, postAuthSetting, putAuthSetting, getPlatformInfo, updateConfig, validateConnection } ================================================ FILE: ui/src/api/chat-user/chat-user.ts ================================================ import type { Ref } from 'vue' import { Result } from '@/request/Result' import { get, put } from '@/request/index' import type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser' import type { pageRequest, PageList } from '@/api/type/common' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() }, }) /** * 获取用户组列表 */ const getUserGroupList: (resource: any, loading?: Ref) => Promise> = (resource, loading) => { return get(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading) } /** * 修改用户组列表授权 */ const editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref) => Promise> = (resource, data, loading) => { return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading) } /** * 获取用户组的用户列表 */ const getUserGroupUserList: ( resource: any, user_group_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise>> = (resource, user_group_id, page, params, loading) => { return get( `${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`, params, loading, ) } /** * 更新用户组的用户列表 */ const putUserGroupUser: ( resource: any, user_group_id: string, data: putUserGroupUserParams[], loading?: Ref, ) => Promise> = (resource, user_group_id, data, loading) => { return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading) } export default { getUserGroupList, editUserGroupList, getUserGroupUserList, putUserGroupUser } ================================================ FILE: ui/src/api/image.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put} from '@/request/index' const prefix = '/oss/file' /** * 上传图片 * @param 参数 file:file */ const postImage: (data: any) => Promise> = (data) => { return post(`${prefix}`, data) } export default { postImage } ================================================ FILE: ui/src/api/knowledge/document.ts ================================================ import { Result } from '@/request/Result' import { del, exportExcel, exportExcelPost, exportFile, exportFilePost, get, post, put } from '@/request/index' import type { Ref } from 'vue' import type { KeyValue, pageRequest } from '@/api/type/common' import useStore from '@/stores' const prefix: any = {_value: '/workspace/'} Object.defineProperty(prefix, 'value', { get: function () { const {user} = useStore() return this._value + user.getWorkspaceId() + '/knowledge' }, }) /** * 文档列表(无分页) * @param 参数 knowledge_id, * param { " name": "string", } */ const getDocumentList: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix.value}/${knowledge_id}/document`, undefined, loading) } /** * 文档分页列表 * @param 参数 knowledge_id, * param { "name": "string", folder_id: "string", } */ const getDocumentPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix.value}/${knowledge_id}/document/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 文档详情 * @param 参数 knowledge_id */ const getDocumentDetail: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return get(`${prefix.value}/${knowledge_id}/document/${document_id}`, {}, loading,) } /** * 修改文档 * @param 参数 * knowledge_id, document_id, * { "name": "string", "is_active": true, "meta": {} } */ const putDocument: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data: any, loading) => { return put(`${prefix.value}/${knowledge_id}/document/${document_id}`, data, undefined, loading) } /** * 删除文档 * @param 参数 knowledge_id, document_id, */ const delDocument: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return del(`${prefix.value}/${knowledge_id}/document/${document_id}`, loading) } /** * 批量取消文档任务 * @param 参数 knowledge_id, *{ "id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "type": 0 } */ const putBatchCancelTask: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading) } /** * 取消文档任务 * @param 参数 knowledge_id, document_id, */ const putCancelTask: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/cancel_task`, data, undefined, loading, ) } /** * 下载原文档 * @param 参数 knowledge_id */ const getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise> = ( knowledge_id, document_id, document_name, ) => { return exportFile(document_name, `${prefix.value}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined) } const postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise> = ( knowledge_id, document_id, data, ) => { return post(`${prefix.value}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocument: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportExcel( document_name.trim() + '.xlsx', `${prefix.value}/${knowledge_id}/document/${document_id}/export`, {}, loading, ) } const exportMulDocument: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportExcelPost( document_name.trim() + '.xlsx', `${prefix.value}/${knowledge_id}/document/batch_export`, {}, document_ids, loading, ) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocumentZip: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportFile( document_name.trim() + '.zip', `${prefix.value}/${knowledge_id}/document/${document_id}/export_zip`, {}, loading, ) } const exportMulDocumentZip: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportFilePost( document_name.trim() + '.zip', `${prefix.value}/${knowledge_id}/document/batch_export_zip`, {}, document_ids, loading, ) } /** * 刷新文档向量库 * @param 参数 * knowledge_id, document_id, * { "state_list": [ "string" ] } */ const putDocumentRefresh: ( knowledge_id: string, document_id: string, state_list: Array, loading?: Ref, ) => Promise> = (knowledge_id, document_id, state_list, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/refresh`, {state_list}, undefined, loading, ) } /** * 同步web站点类型 * @param 参数 * knowledge_id, document_id, */ const putDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 创建批量文档 * @param 参数 { "name": "string", "paragraphs": [ { "content": "string", "title": "string", "problem_list": [ { "id": "string", "content": "string" } ], "is_active": true } ], "source_file_id": string } */ const putMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_create`, data, {}, loading, 1000 * 60 * 5, ) } /** * 批量删除文档 * @param 参数 knowledge_id, * { "id_list": [String] } */ const delMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_delete`, {id_list: data}, undefined, loading, ) } /** * 批量关联 * @param 参数 knowledge_id, { "document_id_list": [ "string" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "state_list": [ "string" ] } */ const putBatchGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_generate_related`, data, undefined, loading, ) } /** * 批量修改命中方式 * @param knowledge_id 知识库id * @param data * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity} * @param loading * @returns */ const putBatchEditHitHandling: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_hit_handling`, data, undefined, loading, ) } /** * 批量刷新文档向量库 * @param knowledge_id 知识库id * @param data { "id_list": [ "string" ], "state_list": [ "string" ] } * @param loading * @returns */ const putBatchRefresh: ( knowledge_id: string, data: any, stateList: Array, loading?: Ref, ) => Promise> = (knowledge_id, data, stateList, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_refresh`, {id_list: data, state_list: stateList}, undefined, loading, ) } /** * 批量同步文档 * @param 参数 knowledge_id, */ const putMulSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/batch_sync`, {id_list: data}, undefined, loading, ) } /** * 批量迁移文档 * @param 参数 knowledge_id,target_knowledge_id, */ const putMigrateMulDocument: ( knowledge_id: string, target_knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, target_knowledge_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/migrate/${target_knowledge_id}`, data, undefined, loading, ) } /** * 导入QA文档 * @param 参数 * file } */ const postQADocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/document/qa`, data, undefined, loading) } /** * 分段预览(上传文档) * @param 参数 file:file,limit:number,patterns:array,with_filter:boolean */ const postSplitDocument: (knowledge_id: string, data: any) => Promise> = ( knowledge_id, data, ) => { return post( `${prefix.value}/${knowledge_id}/document/split`, data, undefined, undefined, 1000 * 60 * 60, ) } /** * 分段标识列表 * @param loading 加载器 * @returns 分段标识列表 */ const listSplitPattern: ( knowledge_id: string, loading?: Ref, ) => Promise>>> = (knowledge_id, loading) => { return get(`${prefix.value}/${knowledge_id}/document/split_pattern`, {}, loading) } /** * 导入表格 * @param 参数 * file */ const postTableDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/document/table`, data, undefined, loading) } /** * 获得QA模板 * @param 参数 fileName,type, */ const exportQATemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel(fileName, `/workspace/knowledge/document/template/export`, {type}, loading) } /** * 获得table模板 * @param 参数 fileName,type, */ const exportTableTemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel( fileName, `/workspace/knowledge/document/table_template/export`, {type}, loading, ) } /** * 创建Web站点文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const postWebDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/document/web`, data, undefined, loading) } /** * 飞书导入获得相关文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const getLarkDocumentList: ( knowledge_id: string, folder_token: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, folder_token, data, loading) => { return post( `${prefix.value}/lark/${knowledge_id}/${folder_token}/doc_list`, data, undefined, loading, ) } /** * 同步飞书文档 */ const putLarkDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix.value}/lark/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 批量同步飞书文档 */ const putMulLarkSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/lark/${knowledge_id}/_batch`, {id_list: data}, undefined, loading) } /** * 导入飞书文档 */ const importLarkDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix.value}/lark/${knowledge_id}/import`, data, null, loading) } const getDocumentTags: ( knowledge_id: string, document_id: string, params: any, loading?: Ref, ) => Promise>> = (knowledge_id, document_id, params, loading) => { return get(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, params, loading) } const postDocumentTags: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, data, null, loading) } const postMulDocumentTags: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/document/batch_add_tag`, data, null, loading) } const delMulDocumentTag: ( knowledge_id: string, document_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, tags, loading) => { return put(`${prefix.value}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading) } const delDocsTag: ( knowledge_id: string, tag_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading) } export default { getDocumentList, getDocumentPage, getDocumentDetail, putDocument, delDocument, putBatchCancelTask, putCancelTask, getDownloadSourceFile, postReplaceSourceFile, exportDocument, exportDocumentZip, exportMulDocument, exportMulDocumentZip, putDocumentRefresh, putDocumentSync, putMulDocument, delMulDocument, putBatchGenerateRelated, putBatchEditHitHandling, putBatchRefresh, putMulSyncDocument, putMigrateMulDocument, postQADocument, postSplitDocument, listSplitPattern, postTableDocument, exportQATemplate, exportTableTemplate, postWebDocument, getLarkDocumentList, putLarkDocumentSync, putMulLarkSyncDocument, importLarkDocument, getDocumentTags, postDocumentTags, postMulDocumentTags, delMulDocumentTag, delDocsTag } ================================================ FILE: ui/src/api/knowledge/knowledge.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile, exportExcel } from '@/request/index' import { type Ref } from 'vue' import type { Dict, pageRequest } from '@/api/type/common' import type { knowledgeData } from '@/api/type/knowledge' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/knowledge' }, }) /** * 知识库列表(无分页) * @param 参数 * param { folder_id: "string", name: "string", tool_type: "string", desc: string, } */ const getKnowledgeList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get(`${prefix.value}`, param, loading) } /** * 知识库分页列表 * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", desc: string, } */ const getKnowledgeListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading) } /** * 知识库详情 * @param 参数 knowledge_id */ const getKnowledgeDetail: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix.value}/${knowledge_id}`, undefined, loading) } /** * 修改知识库信息 * @param 参数 * knowledge_id * { "name": "string", "desc": true } */ const putKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}`, data, undefined, loading) } /** * 删除知识库 * @param 参数 knowledge_id */ const delKnowledge: (knowledge_id: String, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return del(`${prefix.value}/${knowledge_id}`, undefined, {}, loading) } /** * 向量化知识库 * @param 参数 knowledge_id */ const putReEmbeddingKnowledge: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id, loading) => { return put(`${prefix.value}/${knowledge_id}/embedding`, undefined, undefined, loading) } /** * 导出知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @returns */ const exportKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportExcel( knowledge_name + '.xlsx', `${prefix.value}/${knowledge_id}/export`, undefined, loading, ) } /** *导出Zip知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @param loading 加载器 * @returns */ const exportZipKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportFile( knowledge_name + '.zip', `${prefix.value}/${knowledge_id}/export_zip`, undefined, loading, ) } /** * 生成关联问题 * @param knowledge_id 知识库id * @param data * @param loading * @returns */ const putGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/generate_related`, data, null, loading) } /** * 命中测试列表 * @param knowledge_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const putKnowledgeHitTest: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/hit_test`, data, undefined, loading) } /** * 同步知识库 * @param 参数 knowledge_id * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步 */ const putSyncWebKnowledge: ( knowledge_id: string, sync_type: string, loading?: Ref, ) => Promise> = (knowledge_id, sync_type, loading) => { return put(`${prefix.value}/${knowledge_id}/sync`, undefined, { sync_type }, loading) } /** * 创建知识库 * @param 参数 * { "name": "string", "folder_id": "string", "desc": "string", "embedding": "string" } */ const postKnowledge: (data: knowledgeData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}/base`, data, undefined, loading, 1000 * 60 * 5) } /** * 创建工作流知识库 * @param data * @param loading * @returns */ const createWorkflowKnowledge: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}/workflow`, data, undefined, loading) } /** * 获取当前用户可使用的向量化模型列表 (没用到) * @param application_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const getKnowledgeEmdeddingModel: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix.value}/${knowledge_id}/emdedding_model`, loading) } /** * 获取当前用户可使用的模型列表 * @param * @param loading * @returns */ const getKnowledgeModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix.value}/model`, loading) } /** * 创建Web知识库 * @param 参数 * { "name": "string", "folder_id": "string", "desc": "string", "embedding": "string", "source_url": "string", "selector": "string" } */ const postWebKnowledge: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}/web`, data, undefined, loading) } // 创建飞书知识库 const postLarkKnowledge: (data: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return post(`${prefix.value}/lark/save`, data, null, loading) } const putLarkKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/lark/${knowledge_id}`, data, undefined, loading) } const getAllTags: (params: any, loading?: Ref) => Promise> = ( params, loading, ) => { return get(`${prefix.value}/tags`, params, loading) } const getTags: ( knowledge_id: string, params: any, loading?: Ref, ) => Promise> = (knowledge_id, params, loading) => { return get(`${prefix.value}/${knowledge_id}/tags`, params, loading) } const postTags: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return post(`${prefix.value}/${knowledge_id}/tags`, tags, null, loading) } const putTag: ( knowledge_id: string, tag_id: string, tag: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, tag, loading) => { return put(`${prefix.value}/${knowledge_id}/tags/${tag_id}`, tag, null, loading) } const delTag: ( knowledge_id: string, tag_id: string, type: string, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, type, loading) => { return del(`${prefix.value}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading) } const delMulTag: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return put(`${prefix.value}/${knowledge_id}/tags/batch_delete`, tags, null, loading) } const getKnowledgeWorkflowFormList: ( knowledge_id: string, type: 'local' | 'tool', id: string, node: any, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, node, loading, ) => { return post( `${prefix.value}/${knowledge_id}/datasource/${type}/${id}/form_list`, { node }, {}, loading, ) } const getKnowledgeWorkflowDatasourceDetails: ( knowledge_id: string, type: 'local' | 'tool', id: string, params: any, function_name: string, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, params, function_name, loading, ) => { return post( `${prefix.value}/${knowledge_id}/datasource/${type}/${id}/${function_name}`, params, {}, loading, ) } const workflowAction: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix.value}/${knowledge_id}/debug`, instance, {}, loading) } const workflowUpload: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix.value}/${knowledge_id}/upload_document`, instance, {}, loading) } const publish: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id: string, loading, ) => { return put(`${prefix.value}/${knowledge_id}/publish`, {}, {}, loading) } /** * 保存知识库工作流 * @param knowledge_id * @param data * @param loading * @returns */ const putKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/workflow`, data, undefined, loading) } /** * 导出知识库工作流 * @param knowledge_id * @param knowledge_name * @param loading * @returns */ const exportKnowledgeWorkflow = ( knowledge_id: string, knowledge_name: string, loading?: Ref, ) => { return exportFile( knowledge_name + '.kbwf', `${prefix.value}/${knowledge_id}/workflow/export`, undefined, loading, ) } /** * 导入知识库工作流 */ const importKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/workflow/import`, data, undefined, loading) } const listKnowledgeVersion: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, loading) => { return get(`${prefix.value}/${knowledge_id}/knowledge_version`, {}, loading) } const updateKnowledgeVersion: ( knowledge_id: string, knowledge_version_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id: string, knowledge_version_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/knowledge_version/${knowledge_version_id}`, data, {}, loading, ) } const getWorkflowActionPage: ( knowledge_id: string, page: pageRequest, query: any, loading?: Ref, ) => Promise> = (knowledge_id: string, page, query, loading) => { return get( `${prefix.value}/${knowledge_id}/action/${page.current_page}/${page.page_size}`, query, loading, ) } const getWorkflowAction: ( knowledge_id: string, knowledge_action_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, knowledge_action_id, loading) => { return get(`${prefix.value}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading) } const cancelWorkflowAction: ( knowledge_id: string, knowledge_action_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, knowledge_action_id, loading) => { return post( `${prefix.value}/${knowledge_id}/action/${knowledge_action_id}/cancel`, {}, undefined, loading, ) } /** * mcp 节点 */ const getMcpTools: ( knowledge_id: string, mcp_servers: any, loading?: Ref, ) => Promise> = (knowledge_id, mcp_servers, loading) => { return post(`${prefix.value}/${knowledge_id}/mcp_tools`, { mcp_servers }, {}, loading) } const postTransformWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/transform_workflow`, data, undefined, loading) } export default { getKnowledgeList, getKnowledgeListPage, getKnowledgeDetail, putKnowledge, delKnowledge, putReEmbeddingKnowledge, exportKnowledge, exportZipKnowledge, putGenerateRelated, putKnowledgeHitTest, putSyncWebKnowledge, postKnowledge, getKnowledgeModel, postWebKnowledge, postLarkKnowledge, putLarkKnowledge, getAllTags, getTags, postTags, putTag, delTag, delMulTag, createWorkflowKnowledge, getKnowledgeWorkflowFormList, workflowAction, getWorkflowAction, getKnowledgeWorkflowDatasourceDetails, getMcpTools, listKnowledgeVersion, updateKnowledgeVersion, publish, putKnowledgeWorkflow, workflowUpload, getWorkflowActionPage, cancelWorkflowAction, exportKnowledgeWorkflow, importKnowledgeWorkflow, postTransformWorkflow, } ================================================ FILE: ui/src/api/knowledge/paragraph.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { pageRequest } from '@/api/type/common' import type { Ref } from 'vue' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/knowledge' }, }) /** * 创建段落 * @param 参数 * knowledge_id, document_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const postParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph`, data, undefined, loading, ) } /** * 段落分页列表 * @param 参数 knowledge_id document_id * param { "title": "string", "content": "string", } */ const getParagraphPage: ( knowledge_id: string, document_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, page, param, loading) => { return get( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改段落 * @param 参数 * knowledge_id, document_id, paragraph_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const putParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, data, undefined, loading, ) } /** * 删除段落 * @param 参数 knowledge_id, document_id, paragraph_id */ const delParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, loading) => { return del( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, undefined, {}, loading, ) } /** * 某段落问题列表 * @param 参数 knowledge_id,document_id,paragraph_id */ const getParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, ) => Promise> = (knowledge_id, document_id, paragraph_id: string) => { return get(`${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`) } /** * 给某段落创建问题 * @param 参数 * knowledge_id, document_id, paragraph_id * { content": "string" } */ const postParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data: any, loading) => { return post( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`, data, {}, loading, ) } /** * 段落调整顺序 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id new_position 新顺序 * } */ const putAdjustPosition: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`, {}, data, loading, ) } /** * 添加某段落关联问题 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putAssociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/association`, {}, data, loading, ) } /** * 批量删除段落 * @param 参数 knowledge_id, document_id */ const putMulParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`, { id_list: data }, undefined, loading, ) } /** * 批量关联问题 * @param 参数 knowledge_id, document_id * { "paragraph_id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } */ const putBatchGenerateRelated: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`, data, undefined, loading, ) } /** * 批量迁移段落 * @param 参数 knowledge_id,target_knowledge_id, * { "id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ] } */ const putMigrateMulParagraph: ( knowledge_id: string, document_id: string, target_knowledge_id: string, target_document_id: string, data: any, loading?: Ref, ) => Promise> = ( knowledge_id, document_id, target_knowledge_id, target_document_id, data, loading, ) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`, data, undefined, loading, ) } /** * 解除某段落关联问题 * @param 参数 knowledge_id, document_id, * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putDisassociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/unassociation`, {}, data, loading, ) } export default { postParagraph, getParagraphPage, putParagraph, delParagraph, getParagraphProblem, postParagraphProblem, putAssociationProblem, putMulParagraph, putBatchGenerateRelated, putMigrateMulParagraph, putDisassociationProblem, putAdjustPosition } ================================================ FILE: ui/src/api/knowledge/problem.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/knowledge' }, }) /** * 创建问题 * @param 参数 knowledge_id * data: array[string] */ const postProblems: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix.value}/${knowledge_id}/problem`, data, undefined, loading) } /** * 问题分页列表 * @param 参数 knowledge_id, * query { "content": "string", } */ const getProblemsPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix.value}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改问题 * @param 参数 * knowledge_id, problem_id, * { "content": "string", } */ const putProblems: ( knowledge_id: string, problem_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, data: any, loading) => { return put(`${prefix.value}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading) } /** * 删除问题 * @param 参数 knowledge_id, problem_id, */ const delProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return del(`${prefix.value}/${knowledge_id}/problem/${problem_id}`, loading) } /** * 问题详情 * @param 参数 * knowledge_id, problem_id, */ const getDetailProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return get(`${prefix.value}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading) } /** * 批量关联段落 * @param 参数 knowledge_id, * { "problem_id_list": "Array", "paragraph_list": "Array", } */ const putMulAssociationProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/problem/batch_association`, data, undefined, loading) } /** * 批量删除问题 * @param 参数 knowledge_id, * data: array[string] */ const putMulProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix.value}/${knowledge_id}/problem/batch_delete`, data, undefined, loading) } export default { postProblems, getProblemsPage, putProblems, delProblems, getDetailProblems, putMulAssociationProblem, putMulProblem, } ================================================ FILE: ui/src/api/model/model.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' import type { ListModelRequest, Model, CreateModelRequest, EditModelRequest, } from '@/api/type/model' import type { FormField } from '@/components/dynamics-form/type' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() }, }) /** * 获得模型列表 * @params 参数 name, model_type, model_name */ const getModelList: ( data?: ListModelRequest, loading?: Ref, ) => Promise>> = (data, loading) => { return get(`${prefix.value}/model`, data, loading) } /** * 获得下拉选择框模型列表 * @params 参数 name, model_type, model_name */ const getSelectModelList: ( data?: ListModelRequest, loading?: Ref, ) => Promise>> = (data, loading) => { return get(`${prefix.value}/model_list`, data, loading).then((ok) => { return { ...ok, data: [ ...ok.data.shared_model.map((m: any) => { return { ...m, type: 'share' } }), ...ok.data.model.map((m: any) => { return { ...m, type: 'workspace' } }), ], } }) } /** * 获取模型参数表单 * @param model_id 模型id * @param loading * @returns */ const getModelParamsForm: ( model_id: string, loading?: Ref, ) => Promise>> = (model_id, loading) => { return get(`${prefix.value}/model/${model_id}/model_params_form`, {}, loading) } /** * 创建模型 * @param request 请求对象 * @param loading 加载器 * @returns */ const createModel: ( request: CreateModelRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post(`${prefix.value}/model`, request, {}, loading) } /** * 修改模型 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModel: ( model_id: string, request: EditModelRequest, loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix.value}/model/${model_id}`, request, {}, loading) } /** * 修改模型参数配置 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModelParamsForm: ( model_id: string, request: any[], loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix.value}/model/${model_id}/model_params_form`, request, {}, loading) } /** * 获取模型详情根据模型id 包括认证信息 * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix.value}/model/${model_id}`, {}, loading) } /** * 获取模型信息不包括认证信息根据模型id * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelMetaById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix.value}/model/${model_id}/meta`, {}, loading) } /** * 暂停下载 * @param model_id 模型id * @param loading 加载器 * @returns */ const pauseDownload: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return put(`${prefix.value}/model/${model_id}/pause_download`, undefined, {}, loading) } const deleteModel: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return del(`${prefix.value}/model/${model_id}`, undefined, {}, loading) } export default { getModelList, createModel, updateModel, deleteModel, getModelById, getModelMetaById, pauseDownload, getModelParamsForm, updateModelParamsForm, getSelectModelList, } ================================================ FILE: ui/src/api/model/provider.ts ================================================ import {Result} from '@/request/Result' import {get, post} from '@/request/index' import type {Ref} from 'vue' import type {Provider, BaseModel} from '@/api/type/model' import type {FormField} from '@/components/dynamics-form/type' import type {KeyValue} from '../type/common' const prefix_provider = '/provider' /** * 获得供应商列表 */ const getProvider: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix_provider}`, {}, loading) } /** * 获得供应商列表 */ const getProviderByModelType: ( model_type: string, loading?: Ref, ) => Promise>> = (model_type, loading) => { return get(`${prefix_provider}`, {model_type}, loading) } /** * 获取模型创建表单 * @param provider * @param model_type * @param model_name * @param loading * @returns */ const getModelCreateForm: ( provider: string, model_type: string, model_name: string, loading?: Ref, ) => Promise>> = (provider, model_type, model_name, loading) => { return get(`${prefix_provider}/model_form`, {provider, model_type, model_name}, loading) } /** * 获取模型类型列表 * @param provider 供应商 * @param loading 加载器 * @returns 模型类型列表 */ const listModelType: ( provider: string, loading?: Ref, ) => Promise>>> = (provider, loading?: Ref) => { return get(`${prefix_provider}/model_type_list`, {provider}, loading) } /** * 获取基础模型列表 * @param provider * @param model_type * @param loading * @returns */ const listBaseModel: ( provider: string, model_type: string, loading?: Ref, ) => Promise>> = (provider, model_type, loading) => { return get(`${prefix_provider}/model_list`, {provider, model_type}, loading) } const listBaseModelParamsForm: ( provider: string, model_type: string, model_name: string, loading?: Ref, ) => Promise>> = (provider, model_type, model_name, loading) => { return get(`${prefix_provider}/model_params_form`, {provider, model_type, model_name}, loading) } export default { getProvider, getModelCreateForm, getProviderByModelType, listModelType, listBaseModel, listBaseModelParamsForm, } ================================================ FILE: ui/src/api/shared-workspace.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put, exportFile, exportExcel} from '@/request/index' import {type Ref} from 'vue' import type {PageList, pageRequest} from '@/api/type/common' import type {knowledgeData} from '@/api/type/knowledge' import useStore from '@/stores' import type {ChatUserGroupItem} from './type/workspaceChatUser' const prefix = '/system/shared' const prefix_workspace: any = {_value: 'workspace/'} Object.defineProperty(prefix_workspace, 'value', { get: function () { const {user} = useStore() return this._value + user.getWorkspaceId() }, }) const getKnowledgeList: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/${prefix_workspace.value}/knowledge`, {}, loading) } const getKnowledgeListPage: ( page: pageRequest, param: any, loading?: Ref, ) => Promise>> = (page, param, loading) => { return get( `${prefix}/${prefix_workspace.value}/knowledge/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 知识库详情 * @param 参数 knowledge_id */ const getKnowledgeDetail: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}`, undefined, loading) } /** * 文档分页列表 * @param 参数 knowledge_id, * param { "name": "string", folder_id: "string", } */ const getDocumentPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 文档详情 * @param 参数 knowledge_id */ const getDocumentDetail: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return get( `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${document_id}`, {}, loading, ) } /** * 问题分页列表 * @param 参数 knowledge_id, * query { "content": "string", } */ const getProblemsPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/problem/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 获取工作空间下共享知识库用户组的用户列表 */ const getUserGroupUserList: ( resource: any, user_group_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise>> = (resource, user_group_id, page, params, loading) => { return get( `${prefix}/${prefix_workspace.value}/KNOWLEDGE/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`, params, loading, ) } /** * 获取工作空间下共享知识库的用户组 */ const getUserGroupList: (resource: any, loading?: Ref) => Promise> = (resource, loading) => { return get(`${prefix}/${prefix_workspace.value}/KNOWLEDGE/${resource.resource_id}/user_group`, undefined, loading) } /** * 段落分页列表 * @param 参数 knowledge_id document_id * param { "title": "string", "content": "string", } */ const getParagraphPage: ( knowledge_id: string, document_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, page, param, loading) => { return get( `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`, param, loading, ) } const getModelList: (param: any, loading?: Ref) => Promise>> = ( param: any, loading, ) => { return get(`${prefix}/${prefix_workspace.value}/model`, param, loading) } const getToolList: (param: any, loading?: Ref) => Promise>> = (param, loading) => { return get(`${prefix}/${prefix_workspace.value}/tool`, param, loading) } const getToolListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get( `${prefix}/${prefix_workspace.value}/tool/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 获取全部用户 */ const getAllMemberList: (arg: string, loading?: Ref) => Promise[]>> = ( arg, loading, ) => { return get('/user/list', undefined, loading) } const getTags: (knowledge_id: string, params: any, loading?: Ref) => Promise> = ( knowledge_id, params, loading, ) => { return get(`${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/tags`, params, loading) } export default { getKnowledgeList, getKnowledgeListPage, getKnowledgeDetail, getProblemsPage, getDocumentPage, getDocumentDetail, getParagraphPage, getModelList, getToolList, getToolListPage, getUserGroupList, getUserGroupUserList, getAllMemberList, getTags } ================================================ FILE: ui/src/api/system/api-key.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put} from '@/request/index' import {type Ref} from 'vue' const prefix = '/system/api_key' /** * API_KEY列表 */ const getAPIKey: (currentPage: number, pageSize: number, params: any, loading?: Ref) => Promise> = (currentPage: number, pageSize: number, params, loading?: Ref) => { return get(`${prefix}/${currentPage}/${pageSize}`, params) } /** * 新增API_KEY */ const postAPIKey: (loading?: Ref) => Promise> = ( loading ) => { return post(`${prefix}`, {}, undefined, loading) } /** * 删除API_KEY * @param 参数 application_id api_key_id */ const delAPIKey: ( api_key_id: string, loading?: Ref ) => Promise> = (api_key_id, loading) => { return del(`${prefix}/${api_key_id}`, undefined, undefined, loading) } /** * 修改API_KEY * data { * is_active: boolean * } * @param api_key_id * @param data * @param loading */ const putAPIKey: ( api_key_id: string, data: any, loading?: Ref ) => Promise> = (api_key_id, data, loading) => { return put(`${prefix}/${api_key_id}`, data, undefined, loading) } export default { getAPIKey, postAPIKey, delAPIKey, putAPIKey } ================================================ FILE: ui/src/api/system/auth.ts ================================================ import {Result} from '@/request/Result' import {get, post, put} from '@/request/index' import {type Ref} from 'vue' const prefix = '/system/auth' /** * 获取认证设置 */ const getAuthSetting: (auth_type: string, loading?: Ref) => Promise> = (auth_type, loading) => { return get(`${prefix}/${auth_type}/detail`, undefined, loading) } /** * ldap连接测试 */ const postAuthSetting: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}/connection`, data, undefined, loading) } /** * 修改邮箱设置 */ const putAuthSetting: (auth_type: string, data: any, loading?: Ref) => Promise> = ( auth_type, data, loading ) => { return put(`${prefix}/${auth_type}/info`, data, undefined, loading) } export default { getAuthSetting, postAuthSetting, putAuthSetting } ================================================ FILE: ui/src/api/system/chat-user.ts ================================================ import {Result} from '@/request/Result' import {get, put, post, del} from '@/request/index' import type {pageRequest, PageList} from '@/api/type/common' import type {ChatUserItem} from '@/api/type/systemChatUser' import type {Ref} from 'vue' const prefix = '/system/chat_user' /** * 用户列表 */ const getChatUserList: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/list`, undefined, loading) } /** * 用户分页列表 * @query 参数 username_or_nickname: string */ const getUserManage: ( page: pageRequest, params?: any, loading?: Ref, ) => Promise>> = (page, params, loading) => { return get( `${prefix}/user_manage/${page.current_page}/${page.page_size}`, params ? params : undefined, loading, ) } /** * 删除用户 * @param 参数 user_id, */ const delUserManage: (user_id: string, loading?: Ref) => Promise> = ( user_id, loading, ) => { return del(`${prefix}/${user_id}`, undefined, {}, loading) } /** * 创建用户 */ const postUserManage: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 编辑用户 */ const putUserManage: ( user_id: string, data: any, loading?: Ref, ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}`, data, undefined, loading) } /** * 修改用户密码 */ const putUserManagePassword: ( user_id: string, data: any, loading?: Ref ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}/re_password`, data, undefined, loading) } /** * 设置用户组 */ const batchAddGroup: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/batch_add_group`, data, undefined, loading) } /** * 批量删除 */ const batchDelete: (data: string[], loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/batch_delete`, data, undefined, loading) } /** * 同步用户 */ const batchSync: (sync_type: string, loading?: Ref) => Promise> = ( sync_type, loading, ) => { return post(`${prefix}/sync/${sync_type}`, undefined, undefined, loading) } /** * 获取同步类型 */ const getSyncType: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/sync_types`, undefined, loading) } export default { getUserManage, putUserManage, delUserManage, postUserManage, putUserManagePassword, getChatUserList, batchAddGroup, batchDelete, batchSync, getSyncType } ================================================ FILE: ui/src/api/system/license.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' const prefix = '/license' /** * 获得license信息 */ const getLicense: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/profile`, undefined, loading) } /** * 更新license信息 * @param 参数 license_file:file */ const putLicense: (data: any, loading?: Ref) => Promise> = (data, loading) => { return put(`${prefix}/profile`, data, undefined, loading) } export default { getLicense, putLicense } ================================================ FILE: ui/src/api/system/operate-log.ts ================================================ import {Result} from '@/request/Result' import {get, exportExcelPost, post} from '@/request/index' import type {pageRequest} from '@/api/type/common' import {type Ref} from 'vue' const prefix = '/operate_log' /** * 日志分页列表 * @param 参数 * page { "current_page": "string", "page_size": "string", } * @query 参数 param: any */ const getOperateLog: ( page: pageRequest, param: any, loading?: Ref ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } const getMenuList: () => Promise> = () => { return get(`${prefix}/menu_operation_option/`, undefined, undefined) } const exportOperateLog: ( param: any, loading?: Ref ) => void = (param, loading) => { exportExcelPost( 'log.xlsx', `${prefix}/export/`, param, undefined, loading ) } const saveCleanTime: ( data: any, loading?: Ref ) => Promise> = (data, loading) => { return post(`${prefix}/save`, data, undefined, loading) } const getCleanTime: () => Promise> = () => { return get(`${prefix}/get_clean_time`, undefined, undefined) } export default { getOperateLog, getMenuList, exportOperateLog, saveCleanTime, getCleanTime } ================================================ FILE: ui/src/api/system/platform-source.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' const prefix = '/platform' const getPlatformInfo: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/source`, undefined, loading) } const updateConfig: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}/source`, data, undefined, loading) } const validateConnection: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${prefix}/source`, data, undefined, loading) } export default { getPlatformInfo, updateConfig, validateConnection } ================================================ FILE: ui/src/api/system/resource-authorization.ts ================================================ import { Result } from '@/request/Result' import { get, put, post, del } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import { t } from '@/locales/index' const prefix = '/workspace' /** * 系统资源授权获取资源权限 * @query 参数 */ const getResourceAuthorization: ( workspace_id: string, user_id: string, resource: string, params?: any, loading?: Ref, ) => Promise> = (workspace_id, user_id, resource, params, loading) => { return get( `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}`, params, loading, ) } /** * 系统资源授权修改成员权限 * @param 参数 member_id * @param 参数 { [ { "target_id": "string", "permission": "NOT_AUTH" } ] } */ const putResourceAuthorization: ( workspace_id: string, user_id: string, resource: string, body: any, loading?: Ref, ) => Promise> = (workspace_id, user_id, resource, body, loading) => { return put( `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}`, body, {}, loading, ) } /** * 获取成员列表 * @query 参数 */ const getUserList: (workspace_id: string, loading?: Ref) => Promise> = ( workspace_id, loading, ) => { return get(`${prefix}/${workspace_id}/user_list`, undefined, loading) } const getUserMember: (workspace_id: string, loading?: Ref) => Promise> = ( workspace_id, loading, ) => { return get(`${prefix}/${workspace_id}/user_member`, undefined, loading) } /** * 获得系统文件夹列表 * @params 参数 * source : APPLICATION, KNOWLEDGE, TOOL * data : {name: string} */ const getSystemFolder: ( workspace_id: string, source: string, data?: any, loading?: Ref, ) => Promise>> = (workspace_id, source, data, loading) => { if (source == 'MODEL') { return Promise.resolve( Result.success([ { id: 'default', name: t('layout.about.root'), desc: null, parent_id: null, children: [], }, ]), ) } return get(`${prefix}/${workspace_id}/${source}/folder`, data, loading) } export default { getResourceAuthorization, putResourceAuthorization, getUserList, getUserMember, getSystemFolder, } ================================================ FILE: ui/src/api/system/role.ts ================================================ import { get, post, del } from '@/request/index' import type { Ref } from 'vue' import { Result } from '@/request/Result' import type { RoleItem, RolePermissionItem, CreateOrUpdateParams, RoleMemberItem, CreateMemberParamsItem } from '@/api/type/role' import { RoleTypeEnum } from '@/enums/system' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/system/role' /** * 获取角色列表 */ const getRoleList: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}`, undefined, loading) } /** * 根据类型获取角色权限模板列表 */ const getRoleTemplate: (role_type: RoleTypeEnum, loading?: Ref) => Promise> = (role_type, loading) => { return get(`${prefix}/template/${role_type}`, undefined, loading) } /** * 获取角色权限选中 */ const getRolePermissionList: (role_id: string, loading?: Ref) => Promise> = (role_id, loading) => { return get(`${prefix}/${role_id}/permission`, undefined, loading) } /** * 新建或更新角色 */ const CreateOrUpdateRole: ( data: CreateOrUpdateParams, loading?: Ref, ) => Promise> = (data, loading) => { return post(`${prefix}`, data, undefined, loading) } /** * 删除角色 */ const deleteRole: (role_id: string, loading?: Ref) => Promise> = ( role_id, loading, ) => { return del(`${prefix}/${role_id}`, undefined, {}, loading) } /** * 保存角色权限 */ const saveRolePermission: ( role_id: string, data: { id: string, enable: boolean }[], loading?: Ref, ) => Promise> = (role_id, data, loading) => { return post(`${prefix}/${role_id}/permission`, data, undefined, loading) } /** * 获取角色成员列表 */ const getRoleMemberList: ( role_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise>> = (role_id, page, param, loading) => { return get( `${prefix}/${role_id}/user_list/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 新建角色成员 */ const CreateMember: ( role_id: string, data: { members: CreateMemberParamsItem[] }, loading?: Ref, ) => Promise> = (role_id, data, loading) => { return post(`${prefix}/${role_id}/add_member`, data, undefined, loading) } /** * 删除角色成员 */ const deleteRoleMember: (role_id: string, user_relation_id: string, loading?: Ref) => Promise> = ( role_id, user_relation_id, loading, ) => { return del(`${prefix}/${role_id}/remove_member/${user_relation_id}`, undefined, {}, loading) } export default { getRoleList, getRolePermissionList, getRoleTemplate, CreateOrUpdateRole, deleteRole, saveRolePermission, getRoleMemberList, CreateMember, deleteRoleMember } ================================================ FILE: ui/src/api/system/user-group.ts ================================================ import {Result} from '@/request/Result' import {get, post, del} from '@/request/index' import type {Ref} from 'vue' import type {ChatUserGroupUserItem,} from '@/api/type/systemChatUser' import type {pageRequest, PageList, ListItem} from '@/api/type/common' const prefix = '/system/group' /** * 获取用户组列表 */ const getUserGroup: (loading?: Ref) => Promise> = () => { return get(`${prefix}`) } /** * 创建用户组 * @param 参数 * { "id": "string", "name": "string" } */ const postUserGroup: (data: ListItem, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 删除用户组 * @param 参数 user_group_id */ const delUserGroup: (user_group_id: string, loading?: Ref) => Promise> = ( user_group_id, loading, ) => { return del(`${prefix}/${user_group_id}`, undefined, {}, loading) } /** * 给用户组添加用户 */ const postAddMember: ( user_group_id: string, body: any, loading?: Ref, ) => Promise> = (user_group_id, body, loading) => { return post(`${prefix}/${user_group_id}/add_member`, body, {}, loading) } /** * 从用户组删除用户 */ const postRemoveMember: ( user_group_id: string, body: any, loading?: Ref, ) => Promise> = (user_group_id, body, loading) => { return post(`${prefix}/${user_group_id}/remove_member`, body, {}, loading) } /** * 获取用户组的成员列表 */ const getUserListByGroup: ( user_group_id: string, page: pageRequest, params ?: any, loading?: Ref, ) => Promise>> = (user_group_id, page, params, loading) => { return get( `${prefix}/${user_group_id}/user_list/${page.current_page}/${page.page_size}`, params ? params : undefined, loading, ) } export default { getUserGroup, postUserGroup, delUserGroup, postAddMember, postRemoveMember, getUserListByGroup } ================================================ FILE: ui/src/api/system/user-manage.ts ================================================ import {Result} from '@/request/Result' import {get, put, post, del} from '@/request/index' import type {pageRequest} from '@/api/type/common' import type {Ref} from 'vue' const prefix = '/user_manage' /** * 用户分页列表 * @query 参数 email_or_username: string */ const getUserManage: ( page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (page, params, loading) => { return get( `${prefix}/${page.current_page}/${page.page_size}`, params ? params : undefined, loading, ) } /** * 删除用户 * @param 参数 user_id, */ const delUserManage: (user_id: string, loading?: Ref) => Promise> = ( user_id, loading, ) => { return del(`${prefix}/${user_id}`, undefined, {}, loading) } /** * 创建用户 */ const postUserManage: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 编辑用户 */ const putUserManage: ( user_id: string, data: any, loading?: Ref, ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}`, data, undefined, loading) } /** * 修改用户密码 */ const putUserManagePassword: ( user_id: string, data: any, loading?: Ref ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}/re_password`, data, undefined, loading) } /** * 获取系统默认密码 */ const getSystemDefaultPassword: ( loading?: Ref ) => Promise> = (loading) => { return get('/user_manage/password', undefined, loading) } /** * 获取校验 * @param valid_type 校验类型: application|knowledge|user * @param valid_count 校验数量: 5 | 50 | 2 */ const getValid: ( valid_type: string, valid_count: number, loading?: Ref ) => Promise> = (valid_type, valid_count, loading) => { return get(`/valid/${valid_type}/${valid_count}`, undefined, loading) } const batchDelete: ( ids: string[], loading?: Ref ) => Promise> = (ids, loading) => { return post(`/user_manage/batch_delete`, ids, {}, loading) } const batchSetRolePE: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`/user_manage/batch/add_role`, data, undefined, loading) } const batchSetRoleEE: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`/user_manage/batch/add_role_ee`, data, undefined, loading) } export default { getUserManage, putUserManage, delUserManage, postUserManage, putUserManagePassword, getSystemDefaultPassword, getValid, batchDelete, batchSetRolePE, batchSetRoleEE } ================================================ FILE: ui/src/api/system/workspace.ts ================================================ import { Result } from '@/request/Result' import type { Ref } from 'vue' import { get, post, del } from '@/request/index' import type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem } from '@/api/type/workspace' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/system/workspace' /** * 获取首页的工作空间下拉列表 */ const getWorkspaceListByUser: (loading?: Ref) => Promise> = (loading) => { return get('/workspace/by_user', undefined, loading) } /** * 获取添加成员时的工作空间下拉列表 */ const getWorkspaceList: (loading?: Ref) => Promise[]>> = (loading) => { return get('/workspace/current_user', undefined, loading) } /** * 获取工作空间列表 */ const getSystemWorkspaceList: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}`, undefined, loading) } /** * 新建或更新工作空间 */ const CreateOrUpdateWorkspace: ( data: WorkspaceItem, loading?: Ref, ) => Promise> = (data, loading) => { return post(`${prefix}`, data, undefined, loading) } /** * 删除工作空间前的校验 */ const deleteWorkspaceCheck: (workspace_id: string, loading?: Ref) => Promise> = ( workspace_id, loading, ) => { return get(`${prefix}/${workspace_id}/check`, undefined, loading) } /** * 删除工作空间 */ const deleteWorkspace: (workspace_id: string, loading?: Ref) => Promise> = ( workspace_id, loading, ) => { return del(`${prefix}/${workspace_id}`, undefined, {}, loading) } /** * 获取工作空间成员列表 */ const getWorkspaceMemberList: ( workspace_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise>> = (workspace_id, page, param, loading) => { return get( `${prefix}/${workspace_id}/user_list/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 新建工作空间成员 */ const CreateWorkspaceMember: ( workspace_id: string, data: CreateWorkspaceMemberParamsItem[], loading?: Ref, ) => Promise> = (workspace_id, data, loading) => { return post(`${prefix}/${workspace_id}/add_member`, data, undefined, loading) } /** * 删除工作空间成员 */ const deleteWorkspaceMember: (workspace_id: string, user_relation_id: string, loading?: Ref) => Promise> = ( workspace_id, user_relation_id, loading, ) => { return post(`${prefix}/${workspace_id}/remove_member/${user_relation_id}`, undefined, {}, loading) } /** * 获取添加成员时的角色下拉列表 */ const getWorkspaceRoleList: (loading?: Ref) => Promise[]>> = (loading) => { return get('/role_list/current_user', undefined, loading) } export default { getWorkspaceList, getSystemWorkspaceList, CreateOrUpdateWorkspace, deleteWorkspace, getWorkspaceMemberList, CreateWorkspaceMember, deleteWorkspaceMember, getWorkspaceRoleList, getWorkspaceListByUser, deleteWorkspaceCheck } ================================================ FILE: ui/src/api/system-resource-management/application-key.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put} from '@/request/index' import {type Ref} from 'vue' const prefix = '/system/resource/application' /** * API_KEY列表 * @param 参数 application_id */ const getAPIKey: (application_id: string, current_page: number, page_size: number, params?: any, loading?: Ref) => Promise> = ( application_id, current_page, page_size, params, loading, ) => { return get(`${prefix}/${application_id}/application_key/${current_page}/${page_size}`, params, loading) } /** * 新增API_KEY * @param 参数 application_id */ const postAPIKey: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return post(`${prefix}/${application_id}/application_key`, {}, undefined, loading) } /** * 删除API_KEY * @param 参数 application_id api_key_id */ const delAPIKey: ( application_id: string, api_key_id: string, loading?: Ref, ) => Promise> = (application_id, api_key_id, loading) => { return del( `${prefix}/${application_id}/application_key/${api_key_id}`, undefined, undefined, loading, ) } /** * 修改API_KEY * @param 参数 application_id,api_key_id * data { * is_active: boolean * } */ const putAPIKey: ( application_id: string, api_key_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, api_key_id, data, loading) => { return put(`${prefix}/${application_id}/application_key/${api_key_id}`, data, undefined, loading) } export default { getAPIKey, postAPIKey, delAPIKey, putAPIKey, } ================================================ FILE: ui/src/api/system-resource-management/application.ts ================================================ import { Result } from '@/request/Result' import { get, post, postStream, del, put, request, download, exportFile } from '@/request/index' import type { pageRequest } from '@/api/type/common' import type { ApplicationFormType } from '@/api/type/application' import { type Ref } from 'vue' const prefix = '/system/resource/application' /** * 获取全部应用 * @param param * @param loading */ const getAllApplication: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get(`${prefix}`, param, loading) } /** * 获取分页应用 * param { "name": "string", } */ const getApplication: ( page: pageRequest, param: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } /** * 修改应用 * @param 参数 */ const putApplication: ( application_id: string, data: ApplicationFormType, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix}/${application_id}`, data, undefined, loading) } /** * 删除应用 * @param 参数 application_id */ const delApplication: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return del(`${prefix}/${application_id}`, undefined, {}, loading) } /** * 应用详情 * @param 参数 application_id */ const getApplicationDetail: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix}/${application_id}`, undefined, loading) } /** * 获取AccessToken * @param 参数 application_id */ const getAccessToken: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return get(`${prefix}/${application_id}/access_token`, undefined, loading) } /** * 修改AccessToken * @param 参数 application_id * data { * "is_active": true * } */ const putAccessToken: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix}/${application_id}/access_token`, data, undefined, loading) } /** * 替换社区版-修改AccessToken * @param 参数 application_id * data { * "show_source": boolean, * "show_history": boolean, * "draggable": boolean, * "show_guide": boolean, * "avatar": file, * "float_icon": file, * } */ const putXpackAccessToken: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix}/${application_id}/setting`, data, undefined, loading) } /** * 统计 * @param 参数 application_id, data */ const getStatistics: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix}/${application_id}/application_stats`, data, loading) } /** * 统计token消耗 */ const getTokenUsage: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix}/${application_id}/application_token_usage`, data, loading) } const topQuestions: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return get(`${prefix}/${application_id}/top_questions`, data, loading) } /** * 打开调试对话id * @param application_id 应用id * @param loading 加载器 * @returns */ const open: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return get(`${prefix}/${application_id}/open`, {}, loading) } /** * 生成提示词 * @param application_id * @param model_id * @param data * @returns */ const generate_prompt: (application_id:string, model_id:string, data: any) => Promise = ( application_id, model_id, data ) => { const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream(`${prefix}/system/resource/application/${application_id}/model/${model_id}/prompt_generate`, data) } /** * 应用发布 * @param application_id * @param loading * @returns */ const publish: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return put(`${prefix}/${application_id}/publish`, data, {}, loading) } /** * * @param application_id * @param data * @param loading * @returns */ const playDemoText: (application_id: string, data: any, loading?: Ref) => Promise = ( application_id, data, loading, ) => { return download(`${prefix}/${application_id}/play_demo_text`, 'post', data, undefined, loading) } /** * 文本转语音 */ const postTextToSpeech: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return download(`${prefix}/${application_id}/text_to_speech`, 'post', data, undefined, loading) } /** * 语音转文本 */ const speechToText: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return post(`${prefix}/${application_id}/speech_to_text`, data, undefined, loading) } /** * 获取应用设置 * @param application_id 应用id * @param loading 加载器 * @returns */ const getApplicationSetting: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix}/${application_id}/setting`, undefined, loading) } /** * 导出应用 */ const exportApplication = ( application_id: string, application_name: string, loading?: Ref, ) => { return exportFile( application_name + '.mk', `${prefix}/${application_id}/export`, undefined, loading, ) } /** * 导入应用 */ const importApplication: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/import`, data, undefined, loading) } /** * 对话 * @param 参数 * chat_id: string * data */ const chat: (chat_id: string, data: any) => Promise = (chat_id, data) => { const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream(`${prefix}/chat_message/${chat_id}`, data) } /** * 获取对话用户认证类型 * @param loading 加载器 * @returns */ const getChatUserAuthType: (loading?: Ref) => Promise = (loading) => { return get(`/chat_user/auth/types`, {}, loading) } /** * 获取平台状态 */ const getPlatformStatus: (application_id: string) => Promise> = (application_id) => { return get(`${prefix}/${application_id}/platform/status`) } /** * 更新平台状态 */ const updatePlatformStatus: (application_id: string, data: any) => Promise> = ( application_id, data, ) => { return post(`${prefix}/${application_id}/platform/status`, data) } /** * 获取平台配置 */ const getPlatformConfig: (application_id: string, type: string) => Promise> = ( application_id, type, ) => { return get(`${prefix}/${application_id}/platform/${type}`) } /** * 更新平台配置 */ const updatePlatformConfig: ( application_id: string, type: string, data: any, loading?: Ref, ) => Promise> = (application_id, type, data, loading) => { return post(`${prefix}/${application_id}/platform/${type}`, data, undefined, loading) } /** * mcp 节点 */ const getMcpTools: (application_id: string, loading?: Ref) => Promise> = ( application_id, loading, ) => { return get(`${prefix}/${application_id}/mcp_tools`, undefined, loading) } export default { getAllApplication, getApplication, putApplication, delApplication, getApplicationDetail, getAccessToken, putAccessToken, exportApplication, importApplication, getStatistics, open, chat, getChatUserAuthType, getApplicationSetting, getPlatformStatus, updatePlatformStatus, getPlatformConfig, publish, updatePlatformConfig, playDemoText, postTextToSpeech, speechToText, getMcpTools, putXpackAccessToken, generate_prompt, getTokenUsage, topQuestions } ================================================ FILE: ui/src/api/system-resource-management/chat-log.ts ================================================ import { Result } from '@/request/Result' import { get, post, exportExcelPost, del, put } from '@/request/index' import type { pageRequest } from '@/api/type/common' import { type Ref } from 'vue' const prefix = '/system/resource/application' /** * 对话记录提交至知识库 * @param data * @param loading * @param application_id * @param knowledge_id */ const postChatLogAddKnowledge: ( application_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, data, loading) => { return post(`${prefix}/${application_id}/add_knowledge`, data, undefined, loading) } /** * 对话日志 * @param 参数 * application_id * param { "start_time": "string", "end_time": "string", } */ const getChatLog: ( application_id: String, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (application_id, page, param, loading) => { return get( `${prefix}/${application_id}/chat/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 获得对话日志记录 * @param 参数 * application_id, chart_id,order_asc */ const getChatRecordLog: ( application_id: String, chart_id: String, page: pageRequest, loading?: Ref, order_asc?: boolean, ) => Promise> = (application_id, chart_id, page, loading, order_asc) => { return get( `${prefix}/${application_id}/chat/${chart_id}/chat_record/${page.current_page}/${page.page_size}`, { order_asc: order_asc !== undefined ? order_asc : true }, loading, ) } /** * 获取标注段落列表信息 * @param 参数 * application_id, chart_id, chart_record_id */ const getMarkChatRecord: ( application_id: string, chart_id: string, chart_record_id: string, loading?: Ref, ) => Promise> = (application_id, chart_id, chart_record_id, loading) => { return get( `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/improve`, undefined, loading, ) } /** * 修改日志记录内容 * @param 参数 * application_id, chart_id, chart_record_id, knowledge_id, document_id * data { "title": "string", "content": "string", "problem_text": "string" } */ const putChatRecordLog: ( application_id: String, chart_id: String, chart_record_id: String, knowledge_id: String, document_id: String, data: any, loading?: Ref, ) => Promise> = ( application_id, chart_id, chart_record_id, knowledge_id, document_id, data, loading, ) => { return put( `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/improve`, data, undefined, loading, ) } /** * 删除标注 * @param 参数 * application_id, chart_id, chart_record_id, knowledge_id, document_id,paragraph_id */ const delMarkChatRecord: ( application_id: String, chart_id: String, chart_record_id: String, knowledge_id: String, document_id: String, paragraph_id: String, loading?: Ref, ) => Promise> = ( application_id, chart_id, chart_record_id, knowledge_id, document_id, paragraph_id, loading, ) => { return del( `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/improve`, undefined, {}, loading, ) } /** * 导出对话日志 * @param 参数 * application_id * param { "start_time": "string", "end_time": "string", } */ const postExportChatLog: ( application_id: string, application_name: string, param: any, data: any, loading?: Ref, ) => void = (application_id, application_name, param, data, loading) => { exportExcelPost( application_name + '.xlsx', `${prefix}/${application_id}/chat/export`, param, data, loading, ) } const getChatRecordDetails: ( application_id: string, chat_id: string, chat_record_id: string, loading?: Ref, ) => Promise = (application_id, chat_id, chat_record_id, loading) => { return get( `${prefix}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}`, {}, loading, ) } export default { postChatLogAddKnowledge, getChatLog, getChatRecordLog, getMarkChatRecord, putChatRecordLog, delMarkChatRecord, postExportChatLog, getChatRecordDetails, } ================================================ FILE: ui/src/api/system-resource-management/chat-user.ts ================================================ import type {Ref} from 'vue' import {Result} from '@/request/Result' import {get, put } from '@/request/index' import type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/system/resource/knowledge' /** * 获取共享知识库用户组列表 */ const getUserGroupList: (resource: any, loading?: Ref) => Promise> = (resource, loading) => { return get(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading) } /* * 修改共享知识库用户组列表授权 */ const editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref) => Promise> = (resource, data, loading) => { return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading) } /** * 获取共享知识库用户组的用户列表 */ const getUserGroupUserList: ( resource: any, user_group_id: string, page: pageRequest, param?: any, loading?: Ref, ) => Promise>> = (resource, user_group_id, page, param, loading) => { return get( `${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 更新共享知识库用户组的用户列表 */ const putUserGroupUser: ( resource: any, user_group_id:string, data: putUserGroupUserParams[], loading?: Ref, ) => Promise> = (resource, user_group_id, data, loading) => { return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading) } export default { getUserGroupList, editUserGroupList, getUserGroupUserList, putUserGroupUser } ================================================ FILE: ui/src/api/system-resource-management/document.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportExcel, exportFile, exportFilePost, exportExcelPost } from '@/request/index' import type { Ref } from 'vue' import type { KeyValue } from '@/api/type/common' import type { pageRequest } from '@/api/type/common' const prefix = '/system/resource/knowledge' /** * 文档列表(无分页) * @param 参数 knowledge_id, * param { " name": "string", } */ const getDocumentList: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix}/${knowledge_id}/document`, undefined, loading) } /** * 文档分页列表 * @param 参数 knowledge_id, * param { " name": "string", } */ const getDocumentPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/document/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 文档详情 * @param 参数 knowledge_id */ const getDocumentDetail: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return get(`${prefix}/${knowledge_id}/document/${document_id}`, {}, loading) } /** * 修改文档 * @param 参数 * knowledge_id, document_id, * { "name": "string", "is_active": true, "meta": {} } */ const putDocument: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data: any, loading) => { return put(`${prefix}/${knowledge_id}/document/${document_id}`, data, undefined, loading) } /** * 删除文档 * @param 参数 knowledge_id, document_id, */ const delDocument: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return del(`${prefix}/${knowledge_id}/document/${document_id}`, loading) } /** * 批量取消文档任务 * @param 参数 knowledge_id, *{ "id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "type": 0 } */ const putBatchCancelTask: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading) } /** * 取消文档任务 * @param 参数 knowledge_id, document_id, */ const putCancelTask: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/cancel_task`, data, undefined, loading, ) } /** * 下载原文档 * @param 参数 knowledge_id */ const getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise> = ( knowledge_id, document_id, document_name ) => { return exportFile(document_name, `${prefix}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined) } const postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise> = ( knowledge_id, document_id, data, ) => { return post(`${prefix}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocument: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportExcel( document_name.trim() + '.xlsx', `${prefix}/${knowledge_id}/document/${document_id}/export`, {}, loading, ) } const exportMulDocument: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportExcelPost( document_name.trim() + '.xlsx', `${prefix}/${knowledge_id}/document/batch_export`, {}, document_ids, loading, ) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocumentZip: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportFile( document_name.trim() + '.zip', `${prefix}/${knowledge_id}/document/${document_id}/export_zip`, {}, loading, ) } const exportMulDocumentZip: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportFilePost( document_name.trim() + '.zip', `${prefix}/${knowledge_id}/document/batch_export_zip`, {}, document_ids, loading, ) } /** * 刷新文档向量库 * @param 参数 * knowledge_id, document_id, * { "state_list": [ "string" ] } */ const putDocumentRefresh: ( knowledge_id: string, document_id: string, state_list: Array, loading?: Ref, ) => Promise> = (knowledge_id, document_id, state_list, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/refresh`, { state_list }, undefined, loading, ) } /** * 同步web站点类型 * @param 参数 * knowledge_id, document_id, */ const putDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 创建批量文档 * @param 参数 { "name": "string", "paragraphs": [ { "content": "string", "title": "string", "problem_list": [ { "id": "string", "content": "string" } ], "is_active": true } ], "source_file_id": string } */ const putMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_create`, data, {}, loading, 1000 * 60 * 5) } /** * 批量删除文档 * @param 参数 knowledge_id, * { "id_list": [String] } */ const delMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/batch_delete`, { id_list: data }, undefined, loading, ) } /** * 批量关联 * @param 参数 knowledge_id, { "document_id_list": [ "string" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "state_list": [ "string" ] } */ const putBatchGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_generate_related`, data, undefined, loading) } /** * 批量修改命中方式 * @param knowledge_id 知识库id * @param data * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity} * @param loading * @returns */ const putBatchEditHitHandling: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_hit_handling`, data, undefined, loading) } /** * 批量刷新文档向量库 * @param knowledge_id 知识库id * @param data { "id_list": [ "string" ], "state_list": [ "string" ] } * @param loading * @returns */ const putBatchRefresh: ( knowledge_id: string, data: any, stateList: Array, loading?: Ref, ) => Promise> = (knowledge_id, data, stateList, loading) => { return put( `${prefix}/${knowledge_id}/document/batch_refresh`, { id_list: data, state_list: stateList }, undefined, loading, ) } /** * 批量同步文档 * @param 参数 knowledge_id, */ const putMulSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_sync`, { id_list: data }, undefined, loading) } /** * 批量迁移文档 * @param 参数 knowledge_id,target_knowledge_id, */ const putMigrateMulDocument: ( knowledge_id: string, target_knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, target_knowledge_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/migrate/${target_knowledge_id}`, data, undefined, loading, ) } /** * 导入QA文档 * @param 参数 * file } */ const postQADocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/qa`, data, undefined, loading) } /** * 分段预览(上传文档) * @param 参数 file:file,limit:number,patterns:array,with_filter:boolean */ const postSplitDocument: (knowledge_id: string, data: any) => Promise> = ( knowledge_id, data, ) => { return post( `${prefix}/${knowledge_id}/document/split`, data, undefined, undefined, 1000 * 60 * 60, ) } /** * 分段标识列表 * @param loading 加载器 * @returns 分段标识列表 */ const listSplitPattern: ( knowledge_id: string, loading?: Ref, ) => Promise>>> = (knowledge_id, loading) => { return get(`${prefix}/${knowledge_id}/document/split_pattern`, {}, loading) } /** * 导入表格 * @param 参数 * file */ const postTableDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/table`, data, undefined, loading) } /** * 获得QA模板 * @param 参数 fileName,type, */ const exportQATemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel(fileName, `${prefix}/document/template/export`, { type }, loading) } /** * 获得table模板 * @param 参数 fileName,type, */ const exportTableTemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel(fileName, `${prefix}/document/table_template/export`, { type }, loading) } /** * 创建Web站点文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const postWebDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/web`, data, undefined, loading) } /** * 飞书导入获得相关文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const getLarkDocumentList: ( knowledge_id: string, folder_token: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, folder_token, data, loading) => { return post(`${prefix}/lark/${knowledge_id}/${folder_token}/doc_list`, data, undefined, loading) } /** * 同步飞书文档 */ const putLarkDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix}/lark/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 批量同步飞书文档 */ const putMulLarkSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/lark/${knowledge_id}/_batch`, { id_list: data }, undefined, loading) } /** * 导入飞书文档 */ const importLarkDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix}/lark/${knowledge_id}/import`, data, null, loading) } const getDocumentTags: ( knowledge_id: string, document_id: string, params: any, loading?: Ref, ) => Promise>> = (knowledge_id, document_id, params, loading) => { return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading) } const postDocumentTags: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading) } const postMulDocumentTags: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading) } const delMulDocumentTag: ( knowledge_id: string, document_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, tags, loading) => { return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading) } const delDocsTag: ( knowledge_id: string, tag_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, data, loading) => { return put(`${prefix}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading) } export default { getDocumentList, getDocumentPage, getDocumentDetail, putDocument, delDocument, putBatchCancelTask, putCancelTask, getDownloadSourceFile, postReplaceSourceFile, exportDocument, exportDocumentZip, exportMulDocument, exportMulDocumentZip, putDocumentRefresh, putDocumentSync, putMulDocument, delMulDocument, putBatchGenerateRelated, putBatchEditHitHandling, putBatchRefresh, putMulSyncDocument, putMigrateMulDocument, postQADocument, postSplitDocument, listSplitPattern, postTableDocument, postWebDocument, exportQATemplate, exportTableTemplate, getLarkDocumentList, putLarkDocumentSync, putMulLarkSyncDocument, importLarkDocument, getDocumentTags, postDocumentTags, postMulDocumentTags, delMulDocumentTag, delDocsTag } ================================================ FILE: ui/src/api/system-resource-management/folder.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' const prefix = '/system/resource' /** * 获得文件夹列表 * @params 参数 * source : APPLICATION, KNOWLEDGE, TOOL * data : {name: string} */ const getFolder: ( source: string, data?: any, loading?: Ref, ) => Promise>> = (source, data, loading) => { return get(`${prefix}/${source}/folder`, data, loading) } export default { getFolder, } ================================================ FILE: ui/src/api/system-resource-management/knowledge.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile, exportExcel } from '@/request/index' import { type Ref } from 'vue' import type { Dict, pageRequest } from '@/api/type/common' const prefix = '/system/resource/knowledge' /** * 知识库列表(无分页) * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", desc: string, } */ const getKnowledgeList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get(`${prefix}`, param, loading) } /** * 知识库分页列表 * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", desc: string, } */ const getKnowledgeListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } /** * 知识库详情 * @param 参数 knowledge_id */ const getKnowledgeDetail: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix}/${knowledge_id}`, undefined, loading) } /** * 修改知识库信息 * @param 参数 * knowledge_id * { "name": "string", "desc": true } */ const putKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}`, data, undefined, loading) } /** * 删除知识库 * @param 参数 knowledge_id */ const delKnowledge: (knowledge_id: String, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return del(`${prefix}/${knowledge_id}`, undefined, {}, loading) } /** * 向量化知识库 * @param 参数 knowledge_id */ const putReEmbeddingKnowledge: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id, loading) => { return put(`${prefix}/${knowledge_id}/embedding`, undefined, undefined, loading) } /** * 导出知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @returns */ const exportKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportExcel( knowledge_name + '.xlsx', `${prefix}/${knowledge_id}/export`, undefined, loading, ) } /** *导出Zip知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @param loading 加载器 * @returns */ const exportZipKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportFile( knowledge_name + '.zip', `${prefix}/${knowledge_id}/export_zip`, undefined, loading, ) } /** * 生成关联问题 * @param knowledge_id 知识库id * @param data * @param loading * @returns */ const putGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/generate_related`, data, null, loading) } /** * 命中测试列表 * @param knowledge_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const putKnowledgeHitTest: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/hit_test`, data, undefined, loading) } /** * 同步知识库 * @param 参数 knowledge_id * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步 */ const putSyncWebKnowledge: ( knowledge_id: string, sync_type: string, loading?: Ref, ) => Promise> = (knowledge_id, sync_type, loading) => { return put(`${prefix}/${knowledge_id}/sync`, undefined, { sync_type }, loading) } /** * 获取当前用户可使用的向量化模型列表(没用到) * @param application_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const getKnowledgeEmdeddingModel: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix}/${knowledge_id}/emdedding_model`, loading) } /** * 获取当前用户可使用的模型列表 * @param application_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const getKnowledgeModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, loading) } const putLarkKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading) } const getAllTags: (params: any, loading?: Ref) => Promise> = ( params, loading, ) => { return get(`${prefix}/tags`, params, loading) } const getTags: ( knowledge_id: string, params: any, loading?: Ref, ) => Promise> = (knowledge_id, params, loading) => { return get(`${prefix}/${knowledge_id}/tags`, params, loading) } const postTags: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading) } const putTag: ( knowledge_id: string, tag_id: string, tag: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, tag, loading) => { return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading) } const delTag: ( knowledge_id: string, tag_id: string, type: string, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, type, loading) => { return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading) } const delMulTag: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading) } const getKnowledgeWorkflowFormList: ( knowledge_id: string, type: 'local' | 'tool', id: string, node: any, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, node, loading, ) => { return post(`${prefix}/${knowledge_id}/datasource/${type}/${id}/form_list`, { node }, {}, loading) } const getKnowledgeWorkflowDatasourceDetails: ( knowledge_id: string, type: 'local' | 'tool', id: string, params: any, function_name: string, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, params, function_name, loading, ) => { return post( `${prefix}/${knowledge_id}/datasource/${type}/${id}/${function_name}`, params, {}, loading, ) } const workflowAction: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix}/${knowledge_id}/action`, instance, {}, loading) } const getWorkflowActionPage: ( knowledge_id: string, page: pageRequest, query: any, loading?: Ref, ) => Promise> = (knowledge_id: string, page, query, loading) => { return get( `${prefix}/${knowledge_id}/action/${page.current_page}/${page.page_size}`, query, loading, ) } const getWorkflowAction: ( knowledge_id: string, knowledge_action_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, knowledge_action_id, loading) => { return get(`${prefix}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading) } /** * 保存知识库工作流 * @param knowledge_id * @param data * @param loading * @returns */ const putKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/workflow`, data, undefined, loading) } /** * 导出知识库工作流 * @param knowledge_id * @param knowledge_name * @param loading * @returns */ const exportKnowledgeWorkflow = ( knowledge_id: string, knowledge_name: string, loading?: Ref, ) => { return exportFile( knowledge_name + '.kbwf', `${prefix}/${knowledge_id}/workflow/export`, undefined, loading, ) } /** * 导入知识库工作流 * @param knowledge_id * @param data * @param loading * @returns */ const importKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/workflow/import`, data, undefined, loading) } const workflowUpload: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix}/${knowledge_id}/upload_document`, instance, {}, loading) } const publish: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id: string, loading, ) => { return put(`${prefix}/${knowledge_id}/publish`, {}, {}, loading) } const listKnowledgeVersion: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, loading) => { return get(`${prefix}/${knowledge_id}/knowledge_version`, {}, loading) } const postTransformWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/transform_workflow`, data, undefined, loading) } export default { getKnowledgeList, getKnowledgeListPage, getKnowledgeDetail, putKnowledge, delKnowledge, putReEmbeddingKnowledge, exportKnowledge, exportZipKnowledge, putGenerateRelated, putKnowledgeHitTest, putSyncWebKnowledge, getKnowledgeModel, putLarkKnowledge, getAllTags, getTags, postTags, putTag, delTag, delMulTag, getKnowledgeWorkflowFormList, getKnowledgeWorkflowDatasourceDetails, workflowAction, getWorkflowAction, publish, putKnowledgeWorkflow, listKnowledgeVersion, workflowUpload, getWorkflowActionPage, exportKnowledgeWorkflow, importKnowledgeWorkflow, postTransformWorkflow, } as { [key: string]: any } ================================================ FILE: ui/src/api/system-resource-management/model.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' import type { ListModelRequest, Model, CreateModelRequest, EditModelRequest, } from '@/api/type/model' import type { pageRequest } from '@/api/type/common' import type { FormField } from '@/components/dynamics-form/type' const prefix = '/system/resource' /** * 获得模型列表 * @params 参数 name, model_type, model_name */ const getModelListPage: ( page: pageRequest, data?: ListModelRequest, loading?: Ref, ) => Promise>> = (page, data, loading) => { return get(`${prefix}/model/${page.current_page}/${page.page_size}`, data, loading) } /** * 获得下拉选择框模型列表 * @params 参数 name, model_type, model_name */ const getSelectModelList: ( data?: ListModelRequest, loading?: Ref, ) => Promise>> = (data, loading) => { return get(`${prefix}/model/model_list`, data, loading).then((ok) => { return { ...ok, data: [ ...ok.data.shared_model.map((m: any) => { return { ...m, type: 'share' } }), ...ok.data.model.map((m: any) => { return { ...m, type: 'workspace' } }), ], } }) } /** * 获取模型参数表单 * @param model_id 模型id * @param loading * @returns */ const getModelParamsForm: ( model_id: string, loading?: Ref, ) => Promise>> = (model_id, loading) => { return get(`${prefix}/model/${model_id}/model_params_form`, {}, loading) } /** * 创建模型 * @param request 请求对象 * @param loading 加载器 * @returns */ const createModel: ( request: CreateModelRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post(`${prefix}/model`, request, {}, loading) } /** * 修改模型 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModel: ( model_id: string, request: EditModelRequest, loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix}/model/${model_id}`, request, {}, loading) } /** * 修改模型参数配置 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModelParamsForm: ( model_id: string, request: any[], loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix}/model/${model_id}/model_params_form`, request, {}, loading) } /** * 获取模型详情根据模型id 包括认证信息 * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix}/model/${model_id}`, {}, loading) } /** * 获取模型信息不包括认证信息根据模型id * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelMetaById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix}/model/${model_id}/meta`, {}, loading) } /** * 暂停下载 * @param model_id 模型id * @param loading 加载器 * @returns */ const pauseDownload: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return put(`${prefix}/model/${model_id}/pause_download`, undefined, {}, loading) } const deleteModel: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return del(`${prefix}/model/${model_id}`, undefined, {}, loading) } export default { getModelListPage, createModel, updateModel, deleteModel, getModelById, getModelMetaById, pauseDownload, getModelParamsForm, updateModelParamsForm, getSelectModelList, } ================================================ FILE: ui/src/api/system-resource-management/paragraph.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { pageRequest } from '@/api/type/common' import type { Ref } from 'vue' const prefix = '/system/resource/knowledge' /** * 创建段落 * @param 参数 * knowledge_id, document_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const postParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post( `${prefix}/${knowledge_id}/document/${document_id}/paragraph`, data, undefined, loading, ) } /** * 段落列表 * @param 参数 knowledge_id document_id * param { "title": "string", "content": "string", } */ const getParagraphPage: ( knowledge_id: string, document_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改段落 * @param 参数 * knowledge_id, document_id, paragraph_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const putParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, data, undefined, loading, ) } /** * 删除段落 * @param 参数 knowledge_id, document_id, paragraph_id */ const delParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, loading) => { return del( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, undefined, {}, loading, ) } /** * 某段落问题列表 * @param 参数 knowledge_id,document_id,paragraph_id */ const getParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, ) => Promise> = (knowledge_id, document_id, paragraph_id: string) => { return get(`${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`) } /** * 给某段落创建问题 * @param 参数 * knowledge_id, document_id, paragraph_id * { content": "string" } */ const postParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data: any, loading) => { return post( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`, data, {}, loading, ) } /** * 段落调整顺序 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id new_position 新顺序 * } */ const putAdjustPosition: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`, {}, data, loading, ) } /** * 添加某段落关联问题 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putAssociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/association`, {}, data, loading, ) } /** * 批量删除段落 * @param 参数 knowledge_id, document_id */ const putMulParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`, { id_list: data }, undefined, loading, ) } /** * 批量关联问题 * @param 参数 knowledge_id, document_id * { "paragraph_id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } */ const putBatchGenerateRelated: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`, data, undefined, loading, ) } /** * 批量迁移段落 * @param 参数 knowledge_id,target_knowledge_id, */ const putMigrateMulParagraph: ( knowledge_id: string, document_id: string, target_knowledge_id: string, target_document_id: string, data: any, loading?: Ref, ) => Promise> = ( knowledge_id, document_id, target_knowledge_id, target_document_id, data, loading, ) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`, data, undefined, loading, ) } /** * 解除某段落关联问题 * @param 参数 knowledge_id, document_id, * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putDisassociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/unassociation`, {}, data, loading, ) } export default { postParagraph, getParagraphPage, putParagraph, delParagraph, getParagraphProblem, postParagraphProblem, putAssociationProblem, putMulParagraph, putBatchGenerateRelated, putMigrateMulParagraph, putDisassociationProblem, putAdjustPosition, } ================================================ FILE: ui/src/api/system-resource-management/problem.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' const prefix = '/system/resource/knowledge' /** * 创建问题 * @param 参数 knowledge_id * data: array[string] */ const postProblems: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/problem`, data, undefined, loading) } /** * 问题分页列表 * @param 参数 knowledge_id, * query { "content": "string", } */ const getProblemsPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改问题 * @param 参数 * knowledge_id, problem_id, * { "content": "string", } */ const putProblems: ( knowledge_id: string, problem_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, data: any, loading) => { return put(`${prefix}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading) } /** * 删除问题 * @param 参数 knowledge_id, problem_id, */ const delProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return del(`${prefix}/${knowledge_id}/problem/${problem_id}`, loading) } /** * 问题详情 * @param 参数 * knowledge_id, problem_id, */ const getDetailProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return get(`${prefix}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading) } /** * 批量关联段落 * @param 参数 knowledge_id, * { "problem_id_list": "Array", "paragraph_list": "Array", } */ const putMulAssociationProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/problem/batch_association`, data, undefined, loading) } /** * 批量删除问题 * @param 参数 knowledge_id, * data: array[string] */ const putMulProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/problem/batch_delete`, data, undefined, loading) } export default { postProblems, getProblemsPage, putProblems, delProblems, getDetailProblems, putMulAssociationProblem, putMulProblem, } ================================================ FILE: ui/src/api/system-resource-management/resource-authorization.ts ================================================ import { Result } from '@/request/Result' import { get, put, post, del } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' const prefix = 'system/workspace' /** * 系统资源授权获取资源权限 * @query 参数 */ const getResourceAuthorization: ( workspace_id: string, target: string, resource: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (workspace_id, target, resource, page, params, loading) => { return get( `${prefix}/${workspace_id}/resource_management/resource/${target}/resource/${resource}/${page.current_page}/${page.page_size}`, params, loading, ) } /** * 系统资源授权修改成员权限 * @param 参数 member_id * @param 参数 { [ { "target_id": "string", "permission": "NOT_AUTH" } ] } */ const putResourceAuthorization: ( workspace_id: string, target: string, resource: string, body: any, loading?: Ref, ) => Promise> = (workspace_id, target, resource, body, loading) => { return put( `${prefix}/${workspace_id}/resource_management/resource/${target}/resource/${resource}`, body, {}, loading, ) } export default { getResourceAuthorization, putResourceAuthorization, } ================================================ FILE: ui/src/api/system-resource-management/resource-mapping.ts ================================================ import {Result} from '@/request/Result' import {get, put, post, del} from '@/request/index' import type {Ref} from 'vue' import type {pageRequest} from '@/api/type/common' const prefix = '/system/resource' const getResourceMapping: ( workspace_id: string, resource: string, resource_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (workspace_id, resource, resource_id, page, params, loading) => { return get( `${prefix}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`, params, loading, ) } export default { getResourceMapping, } ================================================ FILE: ui/src/api/system-resource-management/tool.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile } from '@/request/index' import { type Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import type { toolData } from '@/api/type/tool' const prefix = '/system/resource/tool' /** * 工具列表带分页(无分页) * @params 参数 * param { "name": "string", "tool_type": "string", } */ const getToolList: (data?: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return get(`${prefix}`, data, loading) } /** * 工具列表带分页(无分页) * @params 参数 * param { "name": "string", "tool_type": "string", } */ const getAllToolList: (data?: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return get(`${prefix}/tool_list`, data, loading) } /** * 工具列表带分页 * @param 参数 * param { "name": "string", "tool_type": "string", } */ const getToolListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } /** * 获取工具详情 * @param tool_id 工具id * @param loading 加载器 * @returns 工具详情 */ const getToolById: (tool_id: string, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return get(`${prefix}/${tool_id}`, undefined, loading) } /** * 修改工具 * @param 参数 */ const putTool: (tool_id: string, data: toolData, loading?: Ref) => Promise> = ( tool_id, data, loading, ) => { return put(`${prefix}/${tool_id}`, data, undefined, loading) } const postToolTestConnection: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/test_connection`, data, undefined, loading) } /** * 删除工具 * @param 参数 tool_id */ const delTool: (tool_id: string, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return del(`${prefix}/${tool_id}`, undefined, {}, loading) } const putToolIcon: (id: string, data: any, loading?: Ref) => Promise> = ( id, data, loading, ) => { return put(`${prefix}/${id}/edit_icon`, data, undefined, loading) } const exportTool = (id: string, name: string, loading?: Ref) => { return exportFile(name + '.tool', `${prefix}/${id}/export`, undefined, loading) } /** * 调试工具 * @param 参数 */ const postToolDebug: (data: any, loading?: Ref) => Promise> = ( data: any, loading, ) => { return post(`${prefix}/debug`, data, undefined, loading) } const postPylint: (code: string, loading?: Ref) => Promise> = ( code, loading, ) => { return post(`${prefix}/pylint`, { code }, {}, loading) } const pageToolRecord = ( tool_id: string, page: pageRequest, param: any, loading?: Ref, ) => { return get( `${prefix}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`, param, loading, ) } const getToolRecordDetail = ( tool_id: string, record_id: string ) => { return get(`${prefix}/${tool_id}/tool_record/${record_id}`) } const uploadSkillFile: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return put(`${prefix}/upload_skill_file`, data, undefined, loading) } export default { getToolListPage, getToolList, getAllToolList, putTool, getToolById, postToolDebug, postPylint, exportTool, putToolIcon, delTool, postToolTestConnection, pageToolRecord, getToolRecordDetail, uploadSkillFile, } ================================================ FILE: ui/src/api/system-resource-management/trigger.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile } from '@/request/index' import { type Ref } from 'vue' import type { TriggerData } from '../type/trigger' const prefix = 'system/resource' /** * 资源端创建触发器 * @param source_type 资源类型 * @param source_id 资源id * @param data 数据 * @param loading 加载器 * @returns */ const postResourceTrigger: ( source_type: string, source_id: string, data: TriggerData, loading?: Ref, ) => Promise> = (source_type, source_id, data, loading) => { return post( `${prefix}/${source_type}/${source_id}/trigger`, data, undefined, loading, ) } /** * 资源端触发器列表 * @param source_type * @param source_id * @param loading * @returns */ const getResourceTriggerList: ( source_type: string, source_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, loading) => { return get( `${prefix}/${source_type}/${source_id}/trigger`, undefined, loading ) } /** * 资源端触发器详情 * @param source_type * @param source_id * @param trigger_id * @param loading * @returns */ const getResourceTriggerDetail: ( source_type: string, source_id: string, trigger_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, loading) => { return get( `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`, undefined, loading ) } /** * 资源端删除触发器 * @param source_type * @param source_id * @param trigger_id * @param loading * @returns */ const deleteResourceTrigger: ( source_type: string, source_id: string, trigger_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, loading) => { return del( `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`, undefined, {}, loading ) } /** * 资源端修改触发器 * @param source_type 资源类型 * @param source_id 资源id * @param trigger_id 触发器id * @param data 触发器数据 * @param loading 加载器 * @returns */ const putResourceTrigger: ( source_type: string, source_id: string, trigger_id: string, data: TriggerData, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, data, loading) => { return put( `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`, data, undefined, loading, ) } export default { postResourceTrigger, getResourceTriggerList, getResourceTriggerDetail, deleteResourceTrigger, putResourceTrigger } ================================================ FILE: ui/src/api/system-resource-management/workflow-version.ts ================================================ import { Result } from '@/request/Result' import { get, put } from '@/request/index' import { type Ref } from 'vue' const prefix = '/system/resource/application' /** * workflow历史版本 */ const getWorkFlowVersion: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { return get(`${prefix}/${application_id}/application_version`, undefined, loading) } /** * workflow历史版本详情 */ const getWorkFlowVersionDetail: ( application_id: string, application_version_id: string, loading?: Ref, ) => Promise> = (application_id, application_version_id, loading) => { return get( `${prefix}/${application_id}/application_version/${application_version_id}`, undefined, loading, ) } /** * 修改workflow历史版本 */ const putWorkFlowVersion: ( application_id: string, application_version_id: string, data: any, loading?: Ref, ) => Promise> = (application_id, application_version_id, data, loading) => { return put( `${prefix}/${application_id}/application_version/${application_version_id}`, data, undefined, loading, ) } export default { getWorkFlowVersion, getWorkFlowVersionDetail, putWorkFlowVersion, } ================================================ FILE: ui/src/api/system-settings/auth-setting.ts ================================================ import {Result} from '@/request/Result' import {get, post, put} from '@/request/index' import {type Ref} from 'vue' const prefix = '/auth' /** * 获取认证设置 */ const getAuthSetting: (auth_type: string, loading?: Ref) => Promise> = (auth_type, loading) => { return get(`${prefix}/${auth_type}/detail`, undefined, loading) } /** * ldap连接测试 */ const postAuthSetting: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}/connection`, data, undefined, loading) } /** * 修改邮箱设置 */ const putAuthSetting: (auth_type: string, data: any, loading?: Ref) => Promise> = ( auth_type, data, loading ) => { return put(`${prefix}/${auth_type}/info`, data, undefined, loading) } /** * 登录设置 */ const putLoginSetting: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${prefix}/setting`, data, undefined, loading) } /** * 获取登录设置 */ const getLoginSetting: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/setting`, undefined, loading) } const getLoginAuthSetting: (loading?: Ref) => Promise> = (loading) => { return get(`login/auth/setting`, undefined, loading) } /** * 获取认证设置 */ const getLoginViewAuthSetting: (auth_type: string, loading?: Ref) => Promise> = (auth_type, loading) => { return get(`login${prefix}/${auth_type}/detail`, undefined, loading) } export default { getAuthSetting, postAuthSetting, putAuthSetting, putLoginSetting, getLoginSetting, getLoginAuthSetting, getLoginViewAuthSetting } ================================================ FILE: ui/src/api/system-settings/email-setting.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { pageRequest } from '@/api/type/common' import { type Ref } from 'vue' const prefix = '/email_setting' /** * 获取邮箱设置 */ const getEmailSetting: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}`, undefined, loading) } /** * 邮箱测试 */ const postTestEmail: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}`, data, undefined, loading) } /** * 修改邮箱设置 */ const putEmailSetting: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${prefix}`, data, undefined, loading) } export default { getEmailSetting, postTestEmail, putEmailSetting } ================================================ FILE: ui/src/api/system-settings/platform-source.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' const prefix = '/platform' const getPlatformInfo: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/source`, undefined, loading) } const updateConfig: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post(`${prefix}/source`, data, undefined, loading) } const validateConnection: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${prefix}/source`, data, undefined, loading) } export default { getPlatformInfo, updateConfig, validateConnection } ================================================ FILE: ui/src/api/system-settings/theme.ts ================================================ import {Result} from '@/request/Result' import {get, post, del, put} from '@/request/index' import type {Ref} from 'vue' const prefix = '/display' /** * 查看外观设置 */ const getThemeInfo: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/info`, undefined, loading) } /** * 更新外观设置 * @param 参数 * * formData { * theme * icon * loginLogo * loginImage * title * slogan * } */ const postThemeInfo: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return put(`${prefix}/update`, data, undefined, loading) } export default { getThemeInfo, postThemeInfo } ================================================ FILE: ui/src/api/system-shared/authorization.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile, exportExcel } from '@/request/index' import { type Ref } from 'vue' const prefix = '/system/shared' const getSharedAuthorizationKnowledge: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix}/knowledge/${knowledge_id}/authorization`, {}, loading) } const postSharedAuthorizationKnowledge: ( knowledge_id: string, param?: any, loading?: Ref, ) => Promise>> = (knowledge_id, param, loading) => { return post(`${prefix}/knowledge/${knowledge_id}/authorization`, param, loading) } const getSharedAuthorizationTool: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix}/tool/${knowledge_id}/authorization`, {}, loading) } const postSharedAuthorizationTool: ( knowledge_id: string, param?: any, loading?: Ref, ) => Promise>> = (knowledge_id, param, loading) => { return post(`${prefix}/tool/${knowledge_id}/authorization`, param, loading) } const getSharedAuthorizationModel: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix}/model/${knowledge_id}/authorization`, {}, loading) } const postSharedAuthorizationModel: ( knowledge_id: string, param?: any, loading?: Ref, ) => Promise>> = (knowledge_id, param, loading) => { return post(`${prefix}/model/${knowledge_id}/authorization`, param, loading) } export default { getSharedAuthorizationKnowledge, postSharedAuthorizationKnowledge, getSharedAuthorizationTool, postSharedAuthorizationTool, getSharedAuthorizationModel, postSharedAuthorizationModel, } as { [key: string]: any } ================================================ FILE: ui/src/api/system-shared/chat-user.ts ================================================ import type {Ref} from 'vue' import {Result} from '@/request/Result' import {get, put } from '@/request/index' import type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/system/shared/knowledge' /** * 获取共享知识库用户组列表 */ const getUserGroupList: (resource: any, loading?: Ref) => Promise> = (resource, loading) => { return get(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading) } /* * 修改共享知识库用户组列表授权 */ const editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref) => Promise> = (resource, data, loading) => { return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading) } /** * 获取共享知识库用户组的用户列表 */ const getUserGroupUserList: ( resource: any, user_group_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise>> = (resource, user_group_id, page, params, loading) => { return get( `${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`, params, loading, ) } /** * 更新共享知识库用户组的用户列表 */ const putUserGroupUser: ( resource: any, user_group_id:string, data: putUserGroupUserParams[], loading?: Ref, ) => Promise> = (resource, user_group_id, data, loading) => { return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading) } export default { getUserGroupList, editUserGroupList, getUserGroupUserList, putUserGroupUser } ================================================ FILE: ui/src/api/system-shared/document.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportExcel, exportFile, exportExcelPost, exportFilePost } from '@/request/index' import type { Ref } from 'vue' import type { KeyValue } from '@/api/type/common' import type { pageRequest } from '@/api/type/common' const prefix = '/system/shared/knowledge' /** * 文档列表(无分页) * @param 参数 knowledge_id, * param { " name": "string", } */ const getDocumentList: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix}/${knowledge_id}/document`, undefined, loading) } /** * 文档分页列表 * @param 参数 knowledge_id, * param { " name": "string", } */ const getDocumentPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/document/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 文档详情 * @param 参数 knowledge_id */ const getDocumentDetail: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return get(`${prefix}/${knowledge_id}/document/${document_id}`, {}, loading) } /** * 修改文档 * @param 参数 * knowledge_id, document_id, * { "name": "string", "is_active": true, "meta": {} } */ const putDocument: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data: any, loading) => { return put(`${prefix}/${knowledge_id}/document/${document_id}`, data, undefined, loading) } /** * 删除文档 * @param 参数 knowledge_id, document_id, */ const delDocument: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return del(`${prefix}/${knowledge_id}/document/${document_id}`, loading) } /** * 批量取消文档任务 * @param 参数 knowledge_id, *{ "id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "type": 0 } */ const putBatchCancelTask: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading) } /** * 取消文档任务 * @param 参数 knowledge_id, document_id, */ const putCancelTask: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/cancel_task`, data, undefined, loading, ) } /** * 下载原文档 * @param 参数 knowledge_id */ const getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise> = ( knowledge_id, document_id, document_name ) => { return exportFile(document_name, `${prefix}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined) } const postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise> = ( knowledge_id, document_id, data, ) => { return post(`${prefix}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocument: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportExcel( document_name.trim() + '.xlsx', `${prefix}/${knowledge_id}/document/${document_id}/export`, {}, loading, ) } const exportMulDocument: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportExcelPost( document_name.trim() + '.xlsx', `${prefix}/${knowledge_id}/document/batch_export`, {}, document_ids, loading, ) } /** * 导出文档 * @param document_name 文档名称 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @returns */ const exportDocumentZip: ( document_name: string, knowledge_id: string, document_id: string, loading?: Ref, ) => Promise = (document_name, knowledge_id, document_id, loading) => { return exportFile( document_name.trim() + '.zip', `${prefix}/${knowledge_id}/document/${document_id}/export_zip`, {}, loading, ) } const exportMulDocumentZip: ( document_name: string, knowledge_id: string, document_ids: string[], loading?: Ref, ) => Promise = (document_name, knowledge_id, document_ids, loading) => { return exportFilePost( document_name.trim() + '.zip', `${prefix}/${knowledge_id}/document/batch_export_zip`, {}, document_ids, loading, ) } /** * 刷新文档向量库 * @param 参数 * knowledge_id, document_id, * { "state_list": [ "string" ] } */ const putDocumentRefresh: ( knowledge_id: string, document_id: string, state_list: Array, loading?: Ref, ) => Promise> = (knowledge_id, document_id, state_list, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/refresh`, { state_list }, undefined, loading, ) } /** * 同步web站点类型 * @param 参数 * knowledge_id, document_id, */ const putDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 创建批量文档 * @param 参数 { "name": "string", "paragraphs": [ { "content": "string", "title": "string", "problem_list": [ { "id": "string", "content": "string" } ], "is_active": true } ], "source_file_id": string } */ const putMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_create`, data, {}, loading, 1000 * 60 * 5) } /** * 批量删除文档 * @param 参数 knowledge_id, * { "id_list": [String] } */ const delMulDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/batch_delete`, { id_list: data }, undefined, loading, ) } /** * 批量关联 * @param 参数 knowledge_id, { "document_id_list": [ "string" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "state_list": [ "string" ] } */ const putBatchGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_generate_related`, data, undefined, loading) } /** * 批量修改命中方式 * @param knowledge_id 知识库id * @param data * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity} * @param loading * @returns */ const putBatchEditHitHandling: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_hit_handling`, data, undefined, loading) } /** * 批量刷新文档向量库 * @param knowledge_id 知识库id * @param data { "id_list": [ "string" ], "state_list": [ "string" ] } * @param loading * @returns */ const putBatchRefresh: ( knowledge_id: string, data: any, stateList: Array, loading?: Ref, ) => Promise> = (knowledge_id, data, stateList, loading) => { return put( `${prefix}/${knowledge_id}/document/batch_refresh`, { id_list: data, state_list: stateList }, undefined, loading, ) } /** * 批量同步文档 * @param 参数 knowledge_id, */ const putMulSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/document/batch_sync`, { id_list: data }, undefined, loading) } /** * 批量迁移文档 * @param 参数 knowledge_id,target_knowledge_id, */ const putMigrateMulDocument: ( knowledge_id: string, target_knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, target_knowledge_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/migrate/${target_knowledge_id}`, data, undefined, loading, ) } /** * 导入QA文档 * @param 参数 * file } */ const postQADocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/qa`, data, undefined, loading) } /** * 分段预览(上传文档) * @param 参数 file:file,limit:number,patterns:array,with_filter:boolean */ const postSplitDocument: (knowledge_id: string, data: any) => Promise> = ( knowledge_id, data, ) => { return post( `${prefix}/${knowledge_id}/document/split`, data, undefined, undefined, 1000 * 60 * 60, ) } /** * 分段标识列表 * @param loading 加载器 * @returns 分段标识列表 */ const listSplitPattern: ( knowledge_id: string, loading?: Ref, ) => Promise>>> = (knowledge_id, loading) => { return get(`${prefix}/${knowledge_id}/document/split_pattern`, {}, loading) } /** * 导入表格 * @param 参数 * file */ const postTableDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/table`, data, undefined, loading) } /** * 获得QA模板 * @param 参数 fileName,type, */ const exportQATemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel(fileName, `${prefix}/document/template/export`, { type }, loading) } /** * 获得table模板 * @param 参数 fileName,type, */ const exportTableTemplate: (fileName: string, type: string, loading?: Ref) => void = ( fileName, type, loading, ) => { return exportExcel(fileName, `${prefix}/document/table_template/export`, { type }, loading) } /** * 创建Web站点文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const postWebDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/web`, data, undefined, loading) } /** * 飞书导入获得相关文档 * @param 参数 * { "source_url_list": [ "string" ], "selector": "string" } } */ const getLarkDocumentList: ( knowledge_id: string, folder_token: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, folder_token, data, loading) => { return post(`${prefix}/lark/${knowledge_id}/${folder_token}/doc_list`, data, undefined, loading) } /** * 同步飞书文档 */ const putLarkDocumentSync: ( knowledge_id: string, document_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, loading) => { return put( `${prefix}/lark/${knowledge_id}/document/${document_id}/sync`, undefined, undefined, loading, ) } /** * 批量同步飞书文档 */ const putMulLarkSyncDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/lark/${knowledge_id}/_batch`, { id_list: data }, undefined, loading) } /** * 导入飞书文档 */ const importLarkDocument: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix}/lark/${knowledge_id}/import`, data, null, loading) } const getDocumentTags: ( knowledge_id: string, document_id: string, params: any, loading?: Ref, ) => Promise>> = (knowledge_id, document_id, params, loading) => { return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading) } const postDocumentTags: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading) } const postMulDocumentTags: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading) } const delMulDocumentTag: ( knowledge_id: string, document_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, tags, loading) => { return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading) } const delDocsTag: ( knowledge_id: string, tag_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, data, loading) => { return put(`${prefix}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading) } export default { getDocumentList, getDocumentPage, getDocumentDetail, putDocument, delDocument, putBatchCancelTask, putCancelTask, getDownloadSourceFile, postReplaceSourceFile, exportDocument, exportDocumentZip, exportMulDocument, exportMulDocumentZip, putDocumentRefresh, putDocumentSync, putMulDocument, delMulDocument, putBatchGenerateRelated, putBatchEditHitHandling, putBatchRefresh, putMulSyncDocument, putMigrateMulDocument, postQADocument, postSplitDocument, listSplitPattern, postTableDocument, postWebDocument, exportQATemplate, exportTableTemplate, getLarkDocumentList, putLarkDocumentSync, putMulLarkSyncDocument, importLarkDocument, getDocumentTags, postDocumentTags, postMulDocumentTags, delMulDocumentTag, delDocsTag } ================================================ FILE: ui/src/api/system-shared/knowledge.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile, exportExcel } from '@/request/index' import { type Ref } from 'vue' import type { Dict, pageRequest } from '@/api/type/common' import type { knowledgeData } from '@/api/type/knowledge' const prefix = '/system/shared/knowledge' /** * 知识库列表(无分页) * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", desc: string, } */ const getKnowledgeList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get(`${prefix}`, param, loading) } /** * 知识库分页列表 * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", desc: string, } */ const getKnowledgeListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } /** * 知识库详情 * @param 参数 knowledge_id */ const getKnowledgeDetail: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return get(`${prefix}/${knowledge_id}`, undefined, loading) } /** * 修改知识库信息 * @param 参数 * knowledge_id * { "name": "string", "desc": true } */ const putKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}`, data, undefined, loading) } /** * 删除知识库 * @param 参数 knowledge_id */ const delKnowledge: (knowledge_id: String, loading?: Ref) => Promise> = ( knowledge_id, loading, ) => { return del(`${prefix}/${knowledge_id}`, undefined, {}, loading) } /** * 向量化知识库 * @param 参数 knowledge_id */ const putReEmbeddingKnowledge: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id, loading) => { return put(`${prefix}/${knowledge_id}/embedding`, undefined, undefined, loading) } /** * 导出知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @returns */ const exportKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportExcel( knowledge_name + '.xlsx', `${prefix}/${knowledge_id}/export`, undefined, loading, ) } /** *导出Zip知识库 * @param knowledge_name 知识库名称 * @param knowledge_id 知识库id * @param loading 加载器 * @returns */ const exportZipKnowledge: ( knowledge_name: string, knowledge_id: string, loading?: Ref, ) => Promise = (knowledge_name, knowledge_id, loading) => { return exportFile( knowledge_name + '.zip', `${prefix}/${knowledge_id}/export_zip`, undefined, loading, ) } /** * 生成关联问题 * @param knowledge_id 知识库id * @param data * @param loading * @returns */ const putGenerateRelated: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/generate_related`, data, null, loading) } /** * 命中测试列表 * @param knowledge_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const putKnowledgeHitTest: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise>> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/hit_test`, data, undefined, loading) } /** * 同步知识库 * @param 参数 knowledge_id * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步 */ const putSyncWebKnowledge: ( knowledge_id: string, sync_type: string, loading?: Ref, ) => Promise> = (knowledge_id, sync_type, loading) => { return put(`${prefix}/${knowledge_id}/sync`, undefined, { sync_type }, loading) } /** * 创建知识库 * @param 参数 * { "name": "string", "folder_id": "string", "desc": "string", "embedding": "string" } */ const postKnowledge: (data: knowledgeData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/base`, data, undefined, loading, 1000 * 60 * 5) } /** * 创建工作流知识库 * @param data * @param loading * @returns */ const createWorkflowKnowledge: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/workflow`, data, undefined, loading) } /** * 获取当前用户可使用的向量化模型列表(没用到) * @param application_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const getKnowledgeEmdeddingModel: ( knowledge_id: string, loading?: Ref, ) => Promise>> = (knowledge_id, loading) => { return get(`${prefix}/${knowledge_id}/emdedding_model`, loading) } /** * 获取当前用户可使用的模型列表 * @param application_id * @param loading * @query { query_text: string, top_number: number, similarity: number } * @returns */ const getKnowledgeModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, loading) } /** * 创建Web知识库 * @param 参数 * { "name": "string", "folder_id": "string", "desc": "string", "embedding": "string", "source_url": "string", "selector": "string" } */ const postWebKnowledge: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/web`, data, undefined, loading) } // 创建飞书知识库 const postLarkKnowledge: (data: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return post(`${prefix}/lark/save`, data, null, loading) } const putLarkKnowledge: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading) } const getAllTags: (params: any, loading?: Ref) => Promise> = ( params, loading, ) => { return get(`${prefix}/tags`, params, loading) } const getTags: ( knowledge_id: string, params: any, loading?: Ref, ) => Promise> = (knowledge_id, params, loading) => { return get(`${prefix}/${knowledge_id}/tags`, params, loading) } const postTags: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading) } const putTag: ( knowledge_id: string, tag_id: string, tag: any, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, tag, loading) => { return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading) } const delTag: ( knowledge_id: string, tag_id: string, type: string, loading?: Ref, ) => Promise> = (knowledge_id, tag_id, type, loading) => { return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading) } const delMulTag: ( knowledge_id: string, tags: any, loading?: Ref, ) => Promise> = (knowledge_id, tags, loading) => { return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading) } const getKnowledgeWorkflowFormList: ( knowledge_id: string, type: 'local' | 'tool', id: string, node: any, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, node, loading, ) => { return post(`${prefix}/${knowledge_id}/datasource/${type}/${id}/form_list`, { node }, {}, loading) } const getKnowledgeWorkflowDatasourceDetails: ( knowledge_id: string, type: 'local' | 'tool', id: string, params: any, function_name: string, loading?: Ref, ) => Promise> = ( knowledge_id: string, type: 'local' | 'tool', id: string, params, function_name, loading, ) => { return post( `${prefix}/${knowledge_id}/datasource/${type}/${id}/${function_name}`, params, {}, loading, ) } const workflowAction: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix}/${knowledge_id}/action`, instance, {}, loading) } const getWorkflowActionPage: ( knowledge_id: string, page: pageRequest, query: any, loading?: Ref, ) => Promise> = (knowledge_id: string, page, query, loading) => { return get( `${prefix}/${knowledge_id}/action/${page.current_page}/${page.page_size}`, query, loading, ) } const getWorkflowAction: ( knowledge_id: string, knowledge_action_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, knowledge_action_id, loading) => { return get(`${prefix}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading) } /** * 保存知识库工作流 * @param knowledge_id * @param data * @param loading * @returns */ const putKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/workflow`, data, undefined, loading) } /** * 导出知识库工作流 * @param knowledge_id * @param knowledge_name * @param loading * @returns */ const exportKnowledgeWorkflow = ( knowledge_id: string, knowledge_name: string, loading?: Ref, ) => { return exportFile( knowledge_name + '.kbwf', `${prefix}/${knowledge_id}/workflow/export`, undefined, loading, ) } /** * 导入知识库工作流 * @param knowledge_id * @param data * @param loading * @returns */ const importKnowledgeWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/workflow/import`, data, undefined, loading) } const workflowUpload: ( knowledge_id: string, instance: Dict, loading?: Ref, ) => Promise> = (knowledge_id: string, instance, loading) => { return post(`${prefix}/${knowledge_id}/upload_document`, instance, {}, loading) } const publish: (knowledge_id: string, loading?: Ref) => Promise> = ( knowledge_id: string, loading, ) => { return put(`${prefix}/${knowledge_id}/publish`, {}, {}, loading) } const listKnowledgeVersion: ( knowledge_id: string, loading?: Ref, ) => Promise> = (knowledge_id: string, loading) => { return get(`${prefix}/${knowledge_id}/knowledge_version`, {}, loading) } const getMcpTools: ( knowledge_id: string, mcp_servers: any, loading?: Ref, ) => Promise> = (knowledge_id, mcp_servers, loading) => { return post(`${prefix}/${knowledge_id}/mcp_tools`, { mcp_servers }, {}, loading) } const postTransformWorkflow: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/transform_workflow`, data, undefined, loading) } export default { getKnowledgeList, getKnowledgeListPage, getKnowledgeDetail, putKnowledge, delKnowledge, putReEmbeddingKnowledge, exportKnowledge, exportZipKnowledge, putGenerateRelated, putKnowledgeHitTest, putSyncWebKnowledge, postKnowledge, getKnowledgeModel, postWebKnowledge, createWorkflowKnowledge, postLarkKnowledge, putLarkKnowledge, getAllTags, getTags, postTags, putTag, delTag, delMulTag, getWorkflowAction, getKnowledgeWorkflowFormList, getKnowledgeWorkflowDatasourceDetails, workflowAction, publish, putKnowledgeWorkflow, listKnowledgeVersion, workflowUpload, getWorkflowActionPage, exportKnowledgeWorkflow, importKnowledgeWorkflow, getMcpTools, postTransformWorkflow, } as { [key: string]: any } ================================================ FILE: ui/src/api/system-shared/model.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' import type { ListModelRequest, Model, CreateModelRequest, EditModelRequest, } from '@/api/type/model' import type { FormField } from '@/components/dynamics-form/type' const prefix = '/system/shared/model' /** * 获得模型列表 * @params 参数 name, model_type, model_name */ const getModelList: ( request?: ListModelRequest, loading?: Ref, ) => Promise>> = (data, loading) => { return get(`${prefix}`, data, loading) } /** * 获得下拉选择框模型列表 * @params 参数 name, model_type, model_name */ const getSelectModelList: ( data?: ListModelRequest, loading?: Ref, ) => Promise>> = (data, loading) => { return get(`${prefix}`, data, loading) } /** * 获取模型参数表单 * @param model_id 模型id * @param loading * @returns */ const getModelParamsForm: ( model_id: string, loading?: Ref, ) => Promise>> = (model_id, loading) => { return get(`${prefix}/${model_id}/model_params_form`, {}, loading) } /** * 创建模型 * @param request 请求对象 * @param loading 加载器 * @returns */ const createModel: ( request: CreateModelRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post(`${prefix}`, request, {}, loading) } /** * 修改模型 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModel: ( model_id: string, request: EditModelRequest, loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix}/${model_id}`, request, {}, loading) } /** * 修改模型参数配置 * @param request 請求對象 * @param loading 加載器 * @returns */ const updateModelParamsForm: ( model_id: string, request: any[], loading?: Ref, ) => Promise> = (model_id, request, loading) => { return put(`${prefix}/${model_id}/model_params_form`, request, {}, loading) } /** * 获取模型详情根据模型id 包括认证信息 * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix}/${model_id}`, {}, loading) } /** * 获取模型信息不包括认证信息根据模型id * @param model_id 模型id * @param loading 加载器 * @returns */ const getModelMetaById: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return get(`${prefix}/${model_id}/meta`, {}, loading) } /** * 暂停下载 * @param model_id 模型id * @param loading 加载器 * @returns */ const pauseDownload: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return put(`${prefix}/${model_id}/pause_download`, undefined, {}, loading) } const deleteModel: (model_id: string, loading?: Ref) => Promise> = ( model_id, loading, ) => { return del(`${prefix}/${model_id}`, undefined, {}, loading) } export default { getModelList, createModel, updateModel, deleteModel, getModelById, getModelMetaById, pauseDownload, getModelParamsForm, updateModelParamsForm, getSelectModelList, } ================================================ FILE: ui/src/api/system-shared/paragraph.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { pageRequest } from '@/api/type/common' import type { Ref } from 'vue' const prefix = '/system/shared/knowledge' /** * 创建段落 * @param 参数 * knowledge_id, document_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const postParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return post( `${prefix}/${knowledge_id}/document/${document_id}/paragraph`, data, undefined, loading, ) } /** * 段落列表 * @param 参数 knowledge_id document_id * param { "title": "string", "content": "string", } */ const getParagraphPage: ( knowledge_id: string, document_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改段落 * @param 参数 * knowledge_id, document_id, paragraph_id * { "content": "string", "title": "string", "is_active": true, "problem_list": [ { "content": "string" } ] } */ const putParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, data, undefined, loading, ) } /** * 删除段落 * @param 参数 knowledge_id, document_id, paragraph_id */ const delParagraph: ( knowledge_id: string, document_id: string, paragraph_id: string, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, loading) => { return del( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`, undefined, {}, loading, ) } /** * 某段落问题列表 * @param 参数 knowledge_id,document_id,paragraph_id */ const getParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, ) => Promise> = (knowledge_id, document_id, paragraph_id: string) => { return get(`${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`) } /** * 给某段落创建问题 * @param 参数 * knowledge_id, document_id, paragraph_id * { content": "string" } */ const postParagraphProblem: ( knowledge_id: string, document_id: string, paragraph_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, paragraph_id, data: any, loading) => { return post( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`, data, {}, loading, ) } /** * 段落调整顺序 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id new_position 新顺序 * } */ const putAdjustPosition: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`, {}, data, loading, ) } /** * 添加某段落关联问题 * @param knowledge_id 数据集id * @param document_id 文档id * @param loading 加载器 * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putAssociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/association`, {}, data, loading, ) } /** * 批量删除段落 * @param 参数 knowledge_id, document_id */ const putMulParagraph: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`, { id_list: data }, undefined, loading, ) } /** * 批量关联问题 * @param 参数 knowledge_id, document_id * { "paragraph_id_list": [ "3fa85f64-5717-4562-b3fc-2c963f66afa6" ], "model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "prompt": "string", "document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } */ const putBatchGenerateRelated: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`, data, undefined, loading, ) } /** * 批量迁移段落 * @param 参数 knowledge_id,target_knowledge_id, */ const putMigrateMulParagraph: ( knowledge_id: string, document_id: string, target_knowledge_id: string, target_document_id: string, data: any, loading?: Ref, ) => Promise> = ( knowledge_id, document_id, target_knowledge_id, target_document_id, data, loading, ) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`, data, undefined, loading, ) } /** * 解除某段落关联问题 * @param 参数 knowledge_id, document_id, * @query data { * paragraph_id 段落id problem_id 问题id * } */ const putDisassociationProblem: ( knowledge_id: string, document_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, document_id, data, loading) => { return put( `${prefix}/${knowledge_id}/document/${document_id}/paragraph/unassociation`, {}, data, loading, ) } export default { postParagraph, getParagraphPage, putParagraph, delParagraph, getParagraphProblem, postParagraphProblem, putAssociationProblem, putMulParagraph, putBatchGenerateRelated, putMigrateMulParagraph, putDisassociationProblem, putAdjustPosition, } ================================================ FILE: ui/src/api/system-shared/problem.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' const prefix = '/system/shared/knowledge' /** * 创建问题 * @param 参数 knowledge_id * data: array[string] */ const postProblems: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return post(`${prefix}/${knowledge_id}/problem`, data, undefined, loading) } /** * 问题分页列表 * @param 参数 knowledge_id, * query { "content": "string", } */ const getProblemsPage: ( knowledge_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise> = (knowledge_id, page, param, loading) => { return get( `${prefix}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 修改问题 * @param 参数 * knowledge_id, problem_id, * { "content": "string", } */ const putProblems: ( knowledge_id: string, problem_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, data: any, loading) => { return put(`${prefix}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading) } /** * 删除问题 * @param 参数 knowledge_id, problem_id, */ const delProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return del(`${prefix}/${knowledge_id}/problem/${problem_id}`, loading) } /** * 问题详情 * @param 参数 * knowledge_id, problem_id, */ const getDetailProblems: ( knowledge_id: string, problem_id: string, loading?: Ref, ) => Promise> = (knowledge_id, problem_id, loading) => { return get(`${prefix}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading) } /** * 批量关联段落 * @param 参数 knowledge_id, * { "problem_id_list": "Array", "paragraph_list": "Array", } */ const putMulAssociationProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/problem/batch_association`, data, undefined, loading) } /** * 批量删除问题 * @param 参数 knowledge_id, * data: array[string] */ const putMulProblem: ( knowledge_id: string, data: any, loading?: Ref, ) => Promise> = (knowledge_id, data, loading) => { return put(`${prefix}/${knowledge_id}/problem/batch_delete`, data, undefined, loading) } export default { postProblems, getProblemsPage, putProblems, delProblems, getDetailProblems, putMulAssociationProblem, putMulProblem, } ================================================ FILE: ui/src/api/system-shared/resource-mapping.ts ================================================ import {Result} from '@/request/Result' import {get, put, post, del} from '@/request/index' import type {Ref} from 'vue' import type {pageRequest} from '@/api/type/common' const prefix = '/system/shared' const getResourceMapping: ( workspace_id: string, resource: string, resource_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (workspace_id, resource, resource_id, page, params, loading) => { return get( `${prefix}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`, params, loading, ) } export default { getResourceMapping, } ================================================ FILE: ui/src/api/system-shared/tool.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile } from '@/request/index' import { type Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import type { toolData, AddInternalToolParam } from '@/api/type/tool' const prefix = '/system/shared/tool' /** * 工具列表带分页(无分页) * @params 参数 {folder_id: string} */ const getToolList: (data?: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return get(`${prefix}`, data, loading) } /** * 工具列表带分页(无分页) */ const getAllToolList: (data?: any, loading?: Ref) => Promise>> = ( data, loading, ) => { return get(`${prefix}/tool_list`, data, loading) } /** * 工具列表带分页 * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", } */ const getToolListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading) } /** * 创建工具 * @param 参数 */ const postTool: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 修改工具 * @param 参数 */ const putTool: (tool_id: string, data: toolData, loading?: Ref) => Promise> = ( tool_id, data, loading, ) => { return put(`${prefix}/${tool_id}`, data, undefined, loading) } /** * @param 参数 */ const postToolTestConnection: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/test_connection`, data, undefined, loading) } /** * 获取工具详情 * @param tool_id 工具id * @param loading 加载器 * @returns 工具详情 */ const getToolById: (tool_id: string, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return get(`${prefix}/${tool_id}`, undefined, loading) } /** * 删除工具 * @param 参数 tool_id */ const delTool: (tool_id: String, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return del(`${prefix}/${tool_id}`, undefined, {}, loading) } const putToolIcon: (id: string, data: any, loading?: Ref) => Promise> = ( id, data, loading, ) => { return put(`${prefix}/${id}/edit_icon`, data, undefined, loading) } const exportTool = (id: string, name: string, loading?: Ref) => { return exportFile(name + '.fx', `${prefix}/${id}/export`, undefined, loading) } /** * 调试工具 * @param 参数 */ const postToolDebug: (data: any, loading?: Ref) => Promise> = ( data: any, loading, ) => { return post(`${prefix}/debug`, data, undefined, loading) } const postImportTool: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/import`, data, undefined, loading) } const postPylint: (code: string, loading?: Ref) => Promise> = ( code, loading, ) => { return post(`${prefix}/pylint`, { code }, {}, loading) } /** * 工具商店-添加系统内置 */ const addInternalTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix}/${tool_id}/add_internal_tool`, param, undefined, loading) } /** * 工具商店 */ const addStoreTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix}/${tool_id}/add_store_tool`, param, undefined, loading) } const updateStoreTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix}/${tool_id}/update_store_tool`, param, undefined, loading) } const pageToolRecord = ( tool_id: string, page: pageRequest, param: any, loading?: Ref, ) => { return get( `${prefix}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`, param, loading, ) } const getToolRecordDetail = ( tool_id: string, record_id: string ) => { return get(`${prefix}/${tool_id}/tool_record/${record_id}`) } const uploadSkillFile: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return put(`${prefix}/upload_skill_file`, data, undefined, loading) } export default { getToolList, getAllToolList, getToolListPage, putTool, getToolById, postTool, postToolDebug, postImportTool, postPylint, exportTool, putToolIcon, delTool, addInternalTool, addStoreTool, updateStoreTool, postToolTestConnection, pageToolRecord, getToolRecordDetail, uploadSkillFile, } ================================================ FILE: ui/src/api/tool/store.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile } from '@/request/index' import { type Ref } from 'vue' import type { AddInternalToolParam } from '@/api/type/tool' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/tool' }, }) /** * 工具商店-系统内置列表 */ const getInternalToolList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get('/workspace/internal/tool', param, loading) } /** * 工具商店列表 */ const getStoreToolList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get('/workspace/store/tool', param, loading) } const getStoreKBList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get('/workspace/store/knowledge_template', param, loading) } const getStoreAppList: (param?: any, loading?: Ref) => Promise> = ( param, loading, ) => { return get('/workspace/store/application_template', param, loading) } /** * 工具商店-添加系统内置 */ const addInternalTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading) } /** * 工具商店-添加 */ const addStoreTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading) } export default { getInternalToolList, getStoreToolList, getStoreKBList, getStoreAppList, addInternalTool, addStoreTool } ================================================ FILE: ui/src/api/tool/tool.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put, exportFile, postStream } from '@/request/index' import { type Ref } from 'vue' import type { pageRequest } from '@/api/type/common' import type { AddInternalToolParam, toolData } from '@/api/type/tool' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/tool' }, }) /** * 工具列表带分页(无分页) * @params 参数 {folder_id: string} */ const getToolList: ( data?: any, loading?: Ref, ) => Promise> = (data, loading) => { return get(`${prefix.value}`, data, loading) } /** * 工具列表带分页(无分页) */ const getAllToolList: ( data?: any, loading?: Ref, ) => Promise> = (data, loading) => { return get(`${prefix.value}/tool_list`, data, loading) } /** * 工具列表带分页 * @param 参数 * param { "folder_id": "string", "name": "string", "tool_type": "string", } */ const getToolListPage: ( page: pageRequest, param?: any, loading?: Ref, ) => Promise> = (page, param, loading) => { return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading) } /** * 创建工具 * @param 参数 */ const postTool: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}`, data, undefined, loading) } /** * 修改工具 * @param 参数 */ const putTool: (tool_id: string, data: toolData, loading?: Ref) => Promise> = ( tool_id, data, loading, ) => { return put(`${prefix.value}/${tool_id}`, data, undefined, loading) } /** * @param 参数 */ const postToolTestConnection: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}/test_connection`, data, undefined, loading) } /** * 获取工具详情 * @param tool_id 工具id * @param loading 加载器 * @returns 函数详情 */ const getToolById: (tool_id: string, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return get(`${prefix.value}/${tool_id}`, undefined, loading) } /** * 删除工具 * @param 参数 tool_id */ const delTool: (tool_id: string, loading?: Ref) => Promise> = ( tool_id, loading, ) => { return del(`${prefix.value}/${tool_id}`, undefined, {}, loading) } const putToolIcon: (id: string, data: any, loading?: Ref) => Promise> = ( id, data, loading, ) => { return put(`${prefix.value}/${id}/edit_icon`, data, undefined, loading) } const exportTool = (id: string, name: string, loading?: Ref) => { return exportFile(name + '.tool', `${prefix.value}/${id}/export`, undefined, loading) } /** * 调试工具 * @param 参数 */ const postToolDebug: (data: any, loading?: Ref) => Promise> = ( data: any, loading, ) => { return post(`${prefix.value}/debug`, data, undefined, loading) } const postImportTool: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}/import`, data, undefined, loading) } const postPylint: (code: string, loading?: Ref) => Promise> = ( code, loading, ) => { return post(`${prefix.value}/pylint`, { code }, {}, loading) } /** * 工具商店-添加系统内置 */ const addInternalTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading) } /** * 工具商店-添加 */ const addStoreTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading) } const updateStoreTool: ( tool_id: string, param: AddInternalToolParam, loading?: Ref, ) => Promise> = (tool_id, param, loading) => { return post(`${prefix.value}/${tool_id}/update_store_tool`, param, undefined, loading) } const pageToolRecord = (tool_id: string, page: pageRequest, param: any, loading?: Ref) => { return get( `${prefix.value}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`, param, loading, ) } const getToolRecordDetail = (tool_id: string, record_id: string) => { return get(`${prefix.value}/${tool_id}/tool_record/${record_id}`) } const uploadSkillFile: (data: toolData, loading?: Ref) => Promise> = ( data, loading, ) => { return put(`${prefix.value}/upload_skill_file`, data, undefined, loading) } /** * 保存工具工作流 * @param tool_id * @param data * @param loading * @returns */ const putToolWorkflow: ( tool_id: string, data: any, loading?: Ref, ) => Promise> = (tool_id, data, loading) => { return put(`${prefix.value}/${tool_id}/workflow`, data, undefined, loading) } /** * 导出知识库工作流 * @param knowledge_id * @param knowledge_name * @param loading * @returns */ const exportKnowledgeWorkflow = ( knowledge_id: string, knowledge_name: string, loading?: Ref, ) => { return exportFile( knowledge_name + '.kbwf', `${prefix.value}/${knowledge_id}/workflow/export`, undefined, loading, ) } /** * 导出知识库工作流 * @param knowledge_id * @param knowledge_name * @param loading * @returns */ const exportToolWorkflow = (tool_id: string, tool_name: string, loading?: Ref) => { return exportFile( tool_name + '.tool', `${prefix.value}/${tool_id}/workflow/export`, undefined, loading, ) } /** * 导入工具工作流 */ const importToolWorkflow: ( tool_id: string, data: any, loading?: Ref, ) => Promise> = (tool_id, data, loading) => { return post(`${prefix.value}/${tool_id}/workflow/import`, data, undefined, loading) } /** * 获取工具工作流版本列表 * @param tool_id * @param loading * @returns */ const listToolWorkflowVersion: (tool_id: string, loading?: Ref) => Promise> = ( tool_id: string, loading, ) => { return get(`${prefix.value}/${tool_id}/tool_version`, {}, loading) } /** * * @param tool_id 工具id * @param tool_version_id 工具版本id * @param data 数据 * @param loading * @returns */ const updateToolWorkflowVersion: ( tool_id: string, tool_version_id: string, data: any, loading?: Ref, ) => Promise> = (tool_id: string, tool_version_id, data, loading) => { return put(`${prefix.value}/${tool_id}/tool_version/${tool_version_id}`, data, {}, loading) } const publish: (tool_id: string, loading?: Ref) => Promise> = ( tool_id: string, loading, ) => { return put(`${prefix.value}/${tool_id}/publish`, {}, {}, loading) } /** * 调试工作流 * @param 参数 * chat_id: string * data */ const debugToolWorkflow: (tool_id: string, data: any) => Promise = (tool_id, data) => { const p = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream(`${p}${prefix.value}/${tool_id}/debug`, data) } export default { getToolList, getAllToolList, getToolListPage, putTool, getToolById, postTool, postToolDebug, postImportTool, postPylint, exportTool, putToolIcon, delTool, addInternalTool, addStoreTool, updateStoreTool, postToolTestConnection, pageToolRecord, getToolRecordDetail, uploadSkillFile, putToolWorkflow, importToolWorkflow, listToolWorkflowVersion, updateToolWorkflowVersion, publish, exportToolWorkflow, debugToolWorkflow, } ================================================ FILE: ui/src/api/trigger/trigger.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import type { User, ResetPasswordRequest, CheckCodeRequest } from '@/api/type/user' import type { Ref } from 'vue' import type { KeyValue, pageRequest } from '@/api/type/common' import useStore from '@/stores' import type { TriggerData } from '../type/trigger' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() + '/trigger' }, }) const prefixWorkspace: any = { _value: '/workspace/' } Object.defineProperty(prefixWorkspace, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() }, }) /** * 触发器列表 * @param data * @param loading * @returns */ const getTriggerList: (data?: any, loading?: Ref) => Promise> = ( data, loading, ) => { return get(`${prefix.value}`, data, loading) } /** * 触发器详情 * @param trigger_id * @param loading * @returns */ const getTriggerDetail: (trigger_id: string, loading?: Ref) => Promise> = ( trigger_id, loading, ) => { return get(`${prefix.value}/${trigger_id}`, {}, loading) } /** * 创建触发器 * @param data * @param loading * @returns */ const postTrigger: (data: TriggerData, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix.value}`, data, undefined, loading) } /** * 修改触发器 * @param trigger_id * @param data * @param loading * @returns */ const putTrigger: ( trigger_id: string, data: TriggerData, loading?: Ref, ) => Promise> = (trigger_id, data, loading) => { return put(`${prefix.value}/${trigger_id}`, data, undefined, loading) } /** * 删除触发器 * @param trigger_id * @param loading * @returns */ const deleteTrigger: (trigger_id: string, loading?: Ref) => Promise> = ( trigger_id, loading, ) => { return del(`${prefix.value}/${trigger_id}`, undefined, {}, loading) } /** * 批量删除触发器 * @param data * @param loading * @returns */ const delMulTrigger: (data: any, loading?: Ref) => Promise> = ( data: any, loading, ) => { return put(`${prefix.value}/batch_delete`, { id_list: data }, undefined, loading) } /** * 批量激活/禁用触发器 * @param data * @param loading * @returns */ const activateMulTrigger: (data: any, loading?: Ref) => Promise> = ( data: any, loading, ) => { return put( `${prefix.value}/batch_activate`, { id_list: data.id_list, is_active: data.is_active }, undefined, loading, ) } /** * 分页查询触发器 * @param page 分页参数 * @param param 查询参数 * @param loading 加载器 * @returns */ const pageTrigger = (page: pageRequest, param: any, loading?: Ref) => { return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading) } /** * 分页查询触发器执行任务 * @param trigger_id 触发器id * @param page 分页参数 * @param param 查询参数 * @param loading 记载器 * @returns */ const pageTriggerTaskRecord = ( trigger_id: string, page: pageRequest, param: any, loading?: Ref, ) => { return get( `${prefix.value}/${trigger_id}/task_record/${page.current_page}/${page.page_size}`, param, loading, ) } const getTriggerTaskRecordDetails = ( trigger_id: string, trigger_task_id: string, trigger_task_record_id: string, loading?: Ref, ) => { return get( `${prefix.value}/${trigger_id}/trigger_task/${trigger_task_id}/trigger_task_record/${trigger_task_record_id}`, {}, loading, ) } /** * 资源端创建触发器 * @param source_type 资源类型 * @param source_id 资源id * @param data 数据 * @param loading 加载器 * @returns */ const postResourceTrigger: ( source_type: string, source_id: string, data: TriggerData, loading?: Ref, ) => Promise> = (source_type, source_id, data, loading) => { return post( `${prefixWorkspace.value}/${source_type}/${source_id}/trigger`, data, undefined, loading, ) } /** * 资源端触发器列表 * @param source_type * @param source_id * @param loading * @returns */ const getResourceTriggerList: ( source_type: string, source_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, loading) => { return get( `${prefixWorkspace.value}/${source_type}/${source_id}/trigger`, undefined, loading ) } /** * 资源端触发器详情 * @param source_type * @param source_id * @param trigger_id * @param loading * @returns */ const getResourceTriggerDetail: ( source_type: string, source_id: string, trigger_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, loading) => { return get( `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`, undefined, loading ) } /** * 资源端删除触发器 * @param source_type * @param source_id * @param trigger_id * @param loading * @returns */ const deleteResourceTrigger: ( source_type: string, source_id: string, trigger_id: string, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, loading) => { return del( `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`, undefined, {}, loading ) } /** * 资源端修改触发器 * @param source_type 资源类型 * @param source_id 资源id * @param trigger_id 触发器id * @param data 触发器数据 * @param loading 加载器 * @returns */ const putResourceTrigger: ( source_type: string, source_id: string, trigger_id: string, data: TriggerData, loading?: Ref, ) => Promise> = (source_type, source_id, trigger_id, data, loading) => { return put( `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`, data, undefined, loading, ) } export default { pageTrigger, getTriggerList, postTrigger, getTriggerDetail, putTrigger, deleteTrigger, delMulTrigger, activateMulTrigger, pageTriggerTaskRecord, getTriggerTaskRecordDetails, postResourceTrigger, putResourceTrigger, getResourceTriggerList, getResourceTriggerDetail, deleteResourceTrigger } ================================================ FILE: ui/src/api/type/application.ts ================================================ import { type Dict } from '@/api/type/common' import { type Ref } from 'vue' import bus from '@/bus' interface ApplicationFormType { name?: string desc?: string model_id?: string dialogue_number?: number prologue?: string knowledge_id_list?: string[] knowledge_setting?: any model_setting?: any problem_optimization?: boolean problem_optimization_prompt?: string icon?: string | undefined type?: string work_flow?: any model_params_setting?: any tts_model_params_setting?: any stt_model_params_setting?: any stt_model_id?: string tts_model_id?: string stt_model_enable?: boolean tts_model_enable?: boolean tts_type?: string tts_autoplay?: boolean stt_autosend?: boolean folder_id?: string workspace_id?: string mcp_enable?: boolean mcp_servers?: string mcp_tool_ids?: string[] mcp_source?: string tool_enable?: boolean tool_ids?: string[] application_enable?: boolean application_ids?: string[] skill_tool_ids?: string[] mcp_output_enable?: boolean work_flow_template?: any } interface Chunk { real_node_id: string chat_id: string chat_record_id: string content: string reasoning_content: string node_id: string up_node_id: string is_end: boolean node_is_end: boolean node_type: string view_type: string runtime_node_id: string child_node: any } interface chatType { id: string problem_text: string answer_text: string buffer: Array answer_text_list: Array< Array<{ content: string reasoning_content: string chat_record_id?: string runtime_node_id?: string child_node?: any real_node_id?: string }> > /** * 是否写入结束 */ write_ed?: boolean /** * 是否暂停 */ is_stop?: boolean record_id: string chat_id: string vote_status: string status?: number execution_details: any[] upload_meta?: { document_list: Array image_list: Array audio_list: Array video_list: Array other_list: Array } } interface Node { buffer: Array node_id: string up_node_id: string node_type: string view_type: string index: number is_end: boolean } interface WriteNodeInfo { current_node: any answer_text_list_index: number current_up_node?: any divider_content?: Array divider_reasoning_content?: Array } export class ChatRecordManage { id?: any ms: number chat: chatType is_close?: boolean write_ed?: boolean is_stop?: boolean loading?: Ref node_list: Array write_node_info?: WriteNodeInfo constructor(chat: chatType, ms?: number, loading?: Ref) { this.ms = ms ? ms : 10 this.chat = chat this.loading = loading this.is_stop = false this.is_close = false this.write_ed = false this.node_list = [] } append_answer( chunk_answer: string, reasoning_content: string, index?: number, chat_record_id?: string, runtime_node_id?: string, child_node?: any, real_node_id?: string, ) { if (chunk_answer || reasoning_content) { const set_index = index != undefined ? index : this.chat.answer_text_list.length - 1 let card_list = this.chat.answer_text_list[set_index] if (!card_list) { card_list = [] this.chat.answer_text_list[set_index] = card_list } const answer_value = card_list.find((item) => item.real_node_id == real_node_id) const content = answer_value ? answer_value.content + chunk_answer : chunk_answer const _reasoning_content = answer_value ? answer_value.reasoning_content + reasoning_content : reasoning_content if (answer_value) { answer_value.content = content answer_value.reasoning_content = _reasoning_content } else { card_list.push({ content: content, reasoning_content: _reasoning_content, chat_record_id, runtime_node_id, child_node, real_node_id, }) } } this.chat.answer_text = this.chat.answer_text + chunk_answer bus.emit('change:answer', { record_id: this.chat.record_id, is_end: false }) } get_current_up_node(run_node: any) { const index = this.node_list.findIndex((item) => item == run_node) if (index > 0) { const n = this.node_list[index - 1] return n } return undefined } get_run_node() { if ( this.write_node_info && (this.write_node_info.current_node.reasoning_content_buffer.length > 0 || this.write_node_info.current_node.buffer.length > 0 || !this.write_node_info.current_node.is_end) ) { return this.write_node_info } const run_node = this.node_list.filter( (item) => item.reasoning_content_buffer.length > 0 || item.buffer.length > 0 || !item.is_end, )[0] if (run_node) { const index = this.node_list.indexOf(run_node) let current_up_node = undefined if (index > 0) { current_up_node = this.get_current_up_node(run_node) } let answer_text_list_index = 0 if ( current_up_node == undefined || run_node.view_type == 'single_view' || current_up_node.view_type == 'single_view' ) { const none_index = this.findIndex( this.chat.answer_text_list, (item) => (item.length == 1 && item[0].content == '') || item.length == 0, 'index', ) if (none_index > -1) { answer_text_list_index = none_index } else { answer_text_list_index = this.chat.answer_text_list.length } } else { const none_index = this.findIndex( this.chat.answer_text_list, (item) => (item.length == 1 && item[0].content == '') || item.length == 0, 'index', ) if (none_index > -1) { answer_text_list_index = none_index } else { answer_text_list_index = this.chat.answer_text_list.length - 1 } } this.write_node_info = { current_node: run_node, current_up_node: current_up_node, answer_text_list_index: answer_text_list_index, } return this.write_node_info } return undefined } findIndex(array: Array, find: (item: T) => boolean, type: 'last' | 'index') { let set_index = -1 for (let index = 0; index < array.length; index++) { const element = array[index] if (find(element)) { set_index = index if (type == 'index') { break } } } return set_index } closeInterval() { this.chat.write_ed = true this.write_ed = true if (this.loading) { this.loading.value = false } bus.emit('change:answer', { record_id: this.chat.record_id, is_end: true }) if (this.id) { clearInterval(this.id) } const last_index = this.findIndex( this.chat.answer_text_list, (item) => (item.length == 1 && item[0].content == '') || item.length == 0, 'last', ) if (last_index > 0) { this.chat.answer_text_list.splice(last_index, 1) } } write() { this.chat.is_stop = false this.is_stop = false if (!this.is_close) { this.is_close = false } this.write_ed = false this.chat.write_ed = false if (this.loading) { this.loading.value = true } this.id = setInterval(() => { const node_info = this.get_run_node() if (node_info == undefined) { if (this.is_close) { this.closeInterval() } return } const { current_node, answer_text_list_index } = node_info if (current_node.buffer.length > 20) { const context = current_node.is_end ? current_node.buffer.splice(0) : current_node.buffer.splice( 0, current_node.is_end ? undefined : current_node.buffer.length - 20, ) const reasoning_content = current_node.is_end ? current_node.reasoning_content_buffer.splice(0) : current_node.reasoning_content_buffer.splice( 0, current_node.is_end ? undefined : current_node.reasoning_content_buffer.length - 20, ) this.append_answer( context.join(''), reasoning_content.join(''), answer_text_list_index, current_node.chat_record_id, current_node.runtime_node_id, current_node.child_node, current_node.real_node_id, ) } else if (this.is_close) { while (true) { const node_info = this.get_run_node() if (node_info == undefined) { break } this.append_answer( node_info.current_node.buffer.splice(0).join(''), node_info.current_node.reasoning_content_buffer.splice(0).join(''), node_info.answer_text_list_index, node_info.current_node.chat_record_id, node_info.current_node.runtime_node_id, node_info.current_node.child_node, node_info.current_node.real_node_id, ) if ( node_info.current_node.buffer.length == 0 && node_info.current_node.reasoning_content_buffer.length == 0 ) { node_info.current_node.is_end = true } } this.closeInterval() } else { const s = current_node.buffer.shift() const reasoning_content = current_node.reasoning_content_buffer.shift() if (s !== undefined) { this.append_answer( s, '', answer_text_list_index, current_node.chat_record_id, current_node.runtime_node_id, current_node.child_node, current_node.real_node_id, ) } if (reasoning_content !== undefined) { this.append_answer( '', reasoning_content, answer_text_list_index, current_node.chat_record_id, current_node.runtime_node_id, current_node.child_node, current_node.real_node_id, ) } } }, this.ms) } stop() { clearInterval(this.id) this.is_stop = true this.chat.is_stop = true if (this.loading) { this.loading.value = false } } close() { this.is_close = true } open() { this.is_close = false this.is_stop = false } appendChunk(chunk: Chunk) { let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id) if (n) { for (const ch of chunk.content) { n.buffer.push(ch) } // n.buffer.push(...chunk.content) n.content += chunk.content if (chunk.reasoning_content) { for (const ch of chunk.reasoning_content) { n.reasoning_content_buffer.push(ch) } // n.reasoning_content_buffer.push(...chunk.reasoning_content) n.reasoning_content += chunk.reasoning_content } } else { n = { buffer: [...chunk.content], reasoning_content_buffer: chunk.reasoning_content ? [...chunk.reasoning_content] : [], reasoning_content: chunk.reasoning_content ? chunk.reasoning_content : '', content: chunk.content, real_node_id: chunk.real_node_id, node_id: chunk.node_id, chat_record_id: chunk.chat_record_id, up_node_id: chunk.up_node_id, runtime_node_id: chunk.runtime_node_id, child_node: chunk.child_node, node_type: chunk.node_type, index: this.node_list.length, view_type: chunk.view_type, is_end: false, } this.node_list.push(n) } if (chunk.node_is_end) { n['is_end'] = true } } append(answer_text_block: string, reasoning_content?: string) { let set_index = this.findIndex( this.chat.answer_text_list, (item) => item.length == 1 && item[0].content == '', 'index', ) if (set_index <= -1) { set_index = 0 } this.chat.answer_text_list[set_index] = [ { content: answer_text_block, reasoning_content: reasoning_content ? reasoning_content : '', }, ] } } export class ChatManagement { static chatMessageContainer: Dict = {} static addChatRecord(chat: chatType, ms: number, loading?: Ref) { this.chatMessageContainer[chat.id] = new ChatRecordManage(chat, ms, loading) } static appendChunk(chatRecordId: string, chunk: Chunk) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.appendChunk(chunk) } } static append(chatRecordId: string, content: string, reasoning_content?: string) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.append(content, reasoning_content) } } static updateStatus(chatRecordId: string, code: number) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.chat.status = code } } /** * 持续从缓存区 写出数据 * @param chatRecordId 对话记录id */ static write(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.write() } } static open(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.open() } } /** * 等待所有数据输出完毕后 才会关闭流 * @param chatRecordId 对话记录id * @returns boolean */ static close(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.close() } } /** * 停止输出 立即关闭定时任务输出 * @param chatRecordId 对话记录id * @returns boolean */ static stop(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] if (chatRecord) { chatRecord.stop() } } /** * 判断是否输出完成 * @param chatRecordId 对话记录id * @returns boolean */ static isClose(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] return chatRecord ? chatRecord.is_close && chatRecord.write_ed : false } /** * 判断是否停止输出 * @param chatRecordId 对话记录id * @returns */ static isStop(chatRecordId: string) { const chatRecord = this.chatMessageContainer[chatRecordId] return chatRecord ? chatRecord.is_stop : false } /** * 清除无用数据 也就是被close掉的和stop的数据 */ static clean() { for (const key in Object.keys(this.chatMessageContainer)) { if (this.chatMessageContainer[key].is_close) { delete this.chatMessageContainer[key] } } } } export type { ApplicationFormType, chatType } ================================================ FILE: ui/src/api/type/chat.ts ================================================ interface ChatProfile { // 是否开启认证 authentication: boolean // icon icon?: string // 应用名称 application_name?: string // 背景图 bg_icon?: string // 认证类型 authentication_type?: 'password' | 'login' // 登录类型 login_value?: Array max_attempts?: number rasKey?: string } interface ChatUserProfile { email: string id: string nick_name: string username: string source: string } export { type ChatProfile, type ChatUserProfile } ================================================ FILE: ui/src/api/type/common.ts ================================================ interface KeyValue { key: K value: V } interface Dict { [propName: string]: V } interface pageRequest { current_page: number page_size: number } interface PageList { current: number, size: number, total: number, records: T } interface ListItem { name: string, id?: string, } export type { KeyValue, Dict, pageRequest, PageList, ListItem } ================================================ FILE: ui/src/api/type/knowledge.ts ================================================ interface knowledgeData { name: string folder_id?: string desc: string embedding_model_id?: string documents?: Array } export type { knowledgeData } ================================================ FILE: ui/src/api/type/login.ts ================================================ interface LoginRequest { /** * 用户名 */ username: string /** * 密码 */ password: string /** * 验证码 */ captcha: string /** * 加密数据 */ encryptedData?: string } export type { LoginRequest } ================================================ FILE: ui/src/api/type/model.ts ================================================ import type { Dict } from './common' interface modelRequest { name: string model_type: string model_name: string } interface Provider { /** * 供应商代号 */ provider: string /** * 供应商名称 */ name: string /** * 供应商icon */ icon: string } interface ListModelRequest { /** * 模型名称 */ name?: string /** * 模型类型 */ model_type?: string /** * 基础模型名称 */ model_name?: string /** * 供应商 */ provider?: string workspace_id?: string } interface Model { /** * 主键id */ id: string /** * 模型名 */ name: string /** * 模型类型 */ model_type: string user_id: string username: string nick_name: string /** * 基础模型 */ model_name: string /** * 认证信息 */ credential: any /** * 供应商 */ provider: string /** * 状态 */ status: 'SUCCESS' | 'DOWNLOAD' | 'ERROR' | 'PAUSE_DOWNLOAD' /** * 元数据 */ meta: Dict /** * 模型参数配置 */ model_params_form: Dict[] resource_count: number create_time?: any } interface CreateModelRequest { /** * 模型名 */ name: string /** * 模型类型 */ model_type: string /** * 基础模型 */ model_name: string /** * 认证信息 */ credential: any /** * 供应商 */ provider: string } interface EditModelRequest { /** * 模型名 */ name: string /** * 模型类型 */ model_type: string /** * 基础模型 */ model_name: string /** * 认证信息 */ credential: any } interface BaseModel { /** * 基础模型名称 */ name: string /** * 基础模型描述 */ desc: string /** * 基础模型类型 */ model_type: string } export type { modelRequest, Provider, ListModelRequest, Model, BaseModel, CreateModelRequest, EditModelRequest } ================================================ FILE: ui/src/api/type/role.ts ================================================ import { RoleTypeEnum } from '@/enums/system' import type { FormItemRule } from 'element-plus' interface RoleItem { id: string, role_name: string, type: RoleTypeEnum, create_user: string, internal: boolean, user_count?: number, } interface ChildrenPermissionItem { id: string name: string enable: boolean } interface RolePermissionItem { id: string, name: string, children: { id: string, name: string, permission: ChildrenPermissionItem[], enable: boolean, }[] } interface RoleTableDataItem { module: string name: string permission: ChildrenPermissionItem[] enable: boolean perChecked: string[] indeterminate: boolean } interface CreateOrUpdateParams { role_id?: string, role_name: string, role_type?: RoleTypeEnum, } interface RoleMemberItem { user_relation_id: string, user_id: string, username: string, nick_name: string, workspace_id: string, workspace_name: string, } interface CreateMemberParamsItem { user_ids: string[], workspace_ids?: string[] } type Arrayable = T | T[] interface FormItemModel { path: string label?: string rules?: Arrayable, hidden?: (e: any) => boolean, selectProps?: { options?: { label: string, value: string, disabledFunction?: (e: any) => boolean }[] placeholder?: string multiple?: boolean clearableFunction?: (e: any) => boolean } } export type { RoleItem, FormItemModel, RolePermissionItem, RoleTableDataItem, CreateOrUpdateParams, ChildrenPermissionItem, RoleMemberItem, CreateMemberParamsItem } ================================================ FILE: ui/src/api/type/systemChatUser.ts ================================================ interface ChatUserItem { create_time: string, email: string, id: string, nick_name: string, phone: string, source: string, update_time: string, username: string, is_active: boolean, user_group_ids?: string[], user_group_names?: string[], } interface ChatUserGroupUserItem { id: string, email: string, phone: string, nick_name: string, username: string, source: string, is_active: boolean, create_time: string, update_time: string, user_group_relation_id: string, } export type { ChatUserGroupUserItem, ChatUserItem } ================================================ FILE: ui/src/api/type/tool.ts ================================================ interface toolData { id?: string name?: string icon?: string desc?: string code?: string input_field_list?: Array init_field_list?: Array is_active?: boolean folder_id?: string tool_type?: string fileList?: Array } interface AddInternalToolParam { name: string, folder_id: string } export type { toolData, AddInternalToolParam } ================================================ FILE: ui/src/api/type/trigger.ts ================================================ interface TriggerData { id?: string name?: string desc?: string trigger_type?: string trigger_setting?: Record meta?: Record is_active?: boolean } export type { TriggerData } ================================================ FILE: ui/src/api/type/user.ts ================================================ interface User { /** * 用户id */ id: string /** * 用户名 */ username: string nick_name: string /** * 邮箱 */ email: string /** * 用户角色 */ role: Array /** * 用户权限 */ permissions: Array /** * 是否需要修改密码 */ is_edit_password?: boolean IS_XPACK?: boolean XPACK_LICENSE_IS_VALID?: boolean language?: string workspace_list?: Array role_name?: Array source?: string } interface LoginRequest { /** * 用户名 */ username: string /** * 密码 */ password: string } interface RegisterRequest { /** * 用户名 */ username: string /** * 密码 */ password: string /** * 确定密码 */ re_password: string /** * 邮箱 */ email: string /** * 验证码 */ code: string } interface CheckCodeRequest { /** * 邮箱 */ email: string /** *验证码 */ code: string /** * 类型 */ type: 'register' | 'reset_password' } interface ResetCurrentUserPasswordRequest { /** * 验证码 */ code?: string /** *密码 */ password: string /** * 确认密码 */ re_password: string } interface ResetPasswordRequest { /** * 邮箱 */ email?: string /** * 验证码 */ code?: string /** * 密码 */ password: string /** * 确认密码 */ re_password: string } export type { LoginRequest, RegisterRequest, CheckCodeRequest, ResetPasswordRequest, User, ResetCurrentUserPasswordRequest, } ================================================ FILE: ui/src/api/type/workspace.ts ================================================ interface WorkspaceItem { name: string, id?: string, user_count?: number, } interface CreateWorkspaceMemberParamsItem { user_ids: string[], role_ids: string[] } interface WorkspaceMemberItem { user_relation_id: string, user_id: string, username: string, nick_name: string, role_id: string, role_name: string, } export type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem } ================================================ FILE: ui/src/api/type/workspaceChatUser.ts ================================================ import { SourceTypeEnum } from '@/enums/common' interface ChatUserGroupItem { id: string, name: string, is_auth: boolean } interface ChatUserGroupUserItem { id: string, is_auth: boolean, email: string, phone: string, nick_name: string, username: string, password: string, source: string, is_active: boolean, create_time: string, update_time: string, } interface putUserGroupUserParams { chat_user_id: string, is_auth: boolean } export type { ChatUserGroupItem, putUserGroupUserParams, ChatUserGroupUserItem } ================================================ FILE: ui/src/api/user/login.ts ================================================ import {Result} from '@/request/Result' import {get, post} from '@/request/index' import type {LoginRequest} from '@/api/type/login' import type {Ref} from 'vue' import type {User} from "@/api/type/user.ts"; /** * 登录 * @param request 登录接口请求表单 * @param loading 接口加载器 * @returns 认证数据 */ const login: (request: LoginRequest, loading?: Ref) => Promise> = ( request, loading, ) => { return post('/user/login', request, undefined, loading) } const ldapLogin: (request: LoginRequest, loading?: Ref) => Promise> = ( request, loading, ) => { return post('/ldap/login', request, undefined, loading) } /** * 登出 * @param loading 接口加载器 * @returns */ const logout: (loading?: Ref) => Promise> = (loading) => { return post('/user/logout', undefined, undefined, loading) } /** * 获取验证码 * @param loading 接口加载器 */ const getCaptcha: (username?: string, loading?: Ref) => Promise> = (username, loading) => { return get('/user/captcha', {username}, loading) } /** * 获取登录方式 */ const getAuthType: (loading?: Ref) => Promise> = (loading) => { return get('auth/types', undefined, loading) } /** * 获取二维码类型 */ const getQrType: (loading?: Ref) => Promise> = (loading) => { return get('qr_type', undefined, loading) } const getQrSource: (loading?: Ref) => Promise> = (loading) => { return get('qr_type/source', undefined, loading) } const getDingCallback: (code: string, loading?: Ref) => Promise> = ( code, loading ) => { return get('dingtalk', {code}, loading) } const getDingOauth2Callback: (code: string, loading?: Ref) => Promise> = ( code, loading ) => { return get('dingtalk/oauth2', {code}, loading) } const getWecomCallback: (code: string, loading?: Ref) => Promise> = ( code, loading ) => { return get('wecom', {code}, loading) } const getLarkCallback: (code: string, loading?: Ref) => Promise> = ( code, loading ) => { return get('lark/oauth2', {code}, loading) } /** * 设置语言 * data: { * "language": "string" * } */ const postLanguage: (data: any, loading?: Ref) => Promise> = ( data, loading ) => { return post('/user/language', data, undefined, loading) } const samlLogin: (loading?: Ref) => Promise> = ( loading, ) => { return get('/saml2', '', loading) } export default { login, logout, getCaptcha, getAuthType, getDingCallback, getQrType, getWecomCallback, postLanguage, getDingOauth2Callback, getLarkCallback, getQrSource, ldapLogin, samlLogin } ================================================ FILE: ui/src/api/user/user.ts ================================================ import { Result } from '@/request/Result' import { get, post } from '@/request/index' import type { User, ResetPasswordRequest, CheckCodeRequest } from '@/api/type/user' import type { Ref } from 'vue' /** * 获取用户基本信息 * @param loading 接口加载器 * @returns 用户基本信息 */ const getUserProfile: (loading?: Ref) => Promise> = (loading) => { return get('/user/profile', undefined, loading) } /** * 获取profile */ const getProfile: (loading?: Ref) => Promise> = (loading) => { return get('/profile', undefined, loading) } /** * 获取全部用户 */ const getUserList: (loading?: Ref) => Promise[]>> = ( loading, ) => { return get('/user/list', undefined, loading) } /** * 获取全部用户 */ const getAllMemberList: (arg: string, loading?: Ref) => Promise[]>> = ( arg, loading, ) => { return get('/user/list', undefined, loading) } /** * 校验验证码 * @param request 请求对象 * @param loading 接口加载器 * @returns */ const checkCode: (request: CheckCodeRequest, loading?: Ref) => Promise> = ( request, loading, ) => { return post('/user/check_code', request, undefined, loading) } /** * 发送邮件 * @param email 邮件地址 * @param loading 接口加载器 * @returns */ const sendEmit: ( email: string, type: 'register' | 'reset_password', loading?: Ref, ) => Promise> = (email, type, loading) => { return post('/user/send_email', { email, type }, undefined, loading) } /** * 重置密码 * @param request 重置密码请求参数 * @param loading 接口加载器 * @returns */ const postResetPassword: ( request: ResetPasswordRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post('/user/re_password', request, undefined, loading) } /** * 重置密码 * @param request 重置密码请求参数 * @param loading 接口加载器 * @returns */ const resetCurrentPassword: ( request: ResetPasswordRequest, loading?: Ref, ) => Promise> = (request, loading) => { return post('/user/current/reset_password', request, undefined, loading) } export default { getUserProfile, getProfile, getUserList, getAllMemberList, postResetPassword, checkCode, sendEmit, resetCurrentPassword, } ================================================ FILE: ui/src/api/workspace/chat-user.ts ================================================ import {Result} from '@/request/Result' import {get, put, post, del} from '@/request/index' import type {pageRequest, PageList} from '@/api/type/common' import type {ChatUserItem} from '@/api/type/systemChatUser' import type {Ref} from 'vue' const prefix = '/workspace/chat_user' /** * 用户列表 */ const getChatUserList: (loading?: Ref) => Promise> = (loading) => { return get(`${prefix}/list`, undefined, loading) } /** * 用户分页列表 * @query 参数 username_or_nickname: string */ const getUserManage: ( page: pageRequest, params?: any, loading?: Ref, ) => Promise>> = (page, params, loading) => { return get( `${prefix}/user_manage/${page.current_page}/${page.page_size}`, params ? params : undefined, loading, ) } /** * 删除用户 * @param 参数 user_id, */ const delUserManage: (user_id: string, loading?: Ref) => Promise> = ( user_id, loading, ) => { return del(`${prefix}/${user_id}`, undefined, {}, loading) } /** * 创建用户 */ const postUserManage: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 编辑用户 */ const putUserManage: ( user_id: string, data: any, loading?: Ref, ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}`, data, undefined, loading) } /** * 修改用户密码 */ const putUserManagePassword: ( user_id: string, data: any, loading?: Ref ) => Promise> = (user_id, data, loading) => { return put(`${prefix}/${user_id}/re_password`, data, undefined, loading) } /** * 设置用户组 */ const batchAddGroup: (data: any, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/batch_add_group`, data, undefined, loading) } /** * 批量删除 */ const batchDelete: (data: string[], loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}/batch_delete`, data, undefined, loading) } export default { getUserManage, putUserManage, delUserManage, postUserManage, putUserManagePassword, getChatUserList, batchAddGroup, batchDelete, } ================================================ FILE: ui/src/api/workspace/folder.ts ================================================ import { Result } from '@/request/Result' import { get, post, del, put } from '@/request/index' import { type Ref } from 'vue' import useStore from '@/stores' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { const { user } = useStore() return this._value + user.getWorkspaceId() }, }) /** * 获得文件夹列表 * @params 参数 * source : APPLICATION, KNOWLEDGE, TOOL * data : {name: string} */ const getFolder: ( source: string, data?: any, loading?: Ref, ) => Promise>> = (source, data, loading) => { return get(`${prefix.value}/${source}/folder`, data, loading) } /** * 添加文件夹 * @params 参数 * source : APPLICATION, KNOWLEDGE, TOOL { "name": "string", "desc": "string", "parent_id": "default" } */ const postFolder: ( source: string, data?: any, loading?: Ref, ) => Promise>> = (source, data, loading) => { return post(`${prefix.value}/${source}/folder`, data, null, loading) } /** * 获得文件夹详情 * @params 参数 * folder_id * source : APPLICATION, KNOWLEDGE, TOOL */ const getFolderDetail: ( folder_id: string, source: string, loading?: Ref, ) => Promise>> = (folder_id, source, loading) => { return get(`${prefix.value}/${source}/folder/${folder_id}`, null, loading) } /** * 修改文件夹 * @params 参数 * folder_id: string, * source : APPLICATION, KNOWLEDGE, TOOL { "name": "string", "desc": "string", "parent_id": "default" } */ const putFolder: ( folder_id: string, source: string, data?: any, loading?: Ref, ) => Promise>> = (folder_id, source, data, loading) => { return put(`${prefix.value}/${source}/folder/${folder_id}`, data, {}, loading) } /** * 删除文件夹 * @params 参数 * folder_id * source : APPLICATION, KNOWLEDGE, TOOL */ const delFolder: ( folder_id: string, source: string, loading?: Ref, ) => Promise> = (folder_id, source, loading) => { return del(`${prefix.value}/${source}/folder/${folder_id}`, undefined, {}, loading) } export default { getFolder, postFolder, getFolderDetail, putFolder, delFolder, } ================================================ FILE: ui/src/api/workspace/resource-authorization.ts ================================================ import { Result } from '@/request/Result' import { get, put, post, del } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' const prefix = '/workspace' /** * 工作空间下各资源获取资源权限 * @query 参数 */ const getResourceAuthorization: ( workspace_id: string, target: string, resource: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (workspace_id, target, resource, page, params, loading) => { return get( `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}/${page.current_page}/${page.page_size}`, params, loading, ) } /** * 工作空间下各资源修改成员权限 * @param 参数 member_id * @param 参数 { [ { "user_id": "string", "permission": "NOT_AUTH" } ] } */ const putResourceAuthorization: ( workspace_id: string, target: string, resource: string, body: any, loading?: Ref, ) => Promise> = (workspace_id, target, resource, body, loading) => { return put( `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}`, body, {}, loading, ) } export default { getResourceAuthorization, putResourceAuthorization } ================================================ FILE: ui/src/api/workspace/resource-mapping.ts ================================================ import { Result } from '@/request/Result' import { get, put, post, del } from '@/request/index' import type { Ref } from 'vue' import type { pageRequest } from '@/api/type/common' const prefix = '/workspace' /** * 工作空间下各个资源的映射关系 * @query 参数 */ const getResourceMapping: ( workspace_id: string, resource: string, resource_id: string, page: pageRequest, params?: any, loading?: Ref, ) => Promise> = (workspace_id, resource, resource_id, page, params, loading) => { return get( `${prefix}/${workspace_id}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`, params, loading, ) } export default { getResourceMapping, } ================================================ FILE: ui/src/api/workspace/role.ts ================================================ import { get, post, del } from '@/request/index' import type { Ref } from 'vue' import { Result } from '@/request/Result' import type { RoleItem, RoleMemberItem, CreateMemberParamsItem, } from '@/api/type/role' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/workspace/role' /** * 获取角色列表 */ const getRoleList: ( loading?: Ref, ) => Promise> = (loading) => { return get(`${prefix}`, undefined, loading) } /** * 新建角色成员 */ const CreateMember: ( role_id: string, data: { members: CreateMemberParamsItem[] }, loading?: Ref, ) => Promise> = (role_id, data, loading) => { return post(`${prefix}/${role_id}/add_member`, data, undefined, loading) } /** * 获取角色成员列表 */ const getRoleMemberList: ( role_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise>> = (role_id, page, param, loading) => { return get( `${prefix}/${role_id}/user_list/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 删除角色成员 */ const deleteRoleMember: ( role_id: string, user_relation_id: string, loading?: Ref, ) => Promise> = (role_id, user_relation_id, loading) => { return del(`${prefix}/${role_id}/remove_member/${user_relation_id}`, undefined, {}, loading) } export default { getRoleList, CreateMember, getRoleMemberList, deleteRoleMember, } ================================================ FILE: ui/src/api/workspace/user-group.ts ================================================ import {Result} from '@/request/Result' import {get, post, del} from '@/request/index' import type {Ref} from 'vue' import type {ChatUserGroupUserItem,} from '@/api/type/systemChatUser' import type {pageRequest, PageList, ListItem} from '@/api/type/common' const prefix = '/workspace/group' /** * 获取用户组列表 */ const getUserGroup: (loading?: Ref) => Promise> = () => { return get(`${prefix}`) } /** * 创建用户组 * @param 参数 * { "id": "string", "name": "string" } */ const postUserGroup: (data: ListItem, loading?: Ref) => Promise> = ( data, loading, ) => { return post(`${prefix}`, data, undefined, loading) } /** * 删除用户组 * @param 参数 user_group_id */ const delUserGroup: (user_group_id: string, loading?: Ref) => Promise> = ( user_group_id, loading, ) => { return del(`${prefix}/${user_group_id}`, undefined, {}, loading) } /** * 给用户组添加用户 */ const postAddMember: ( user_group_id: string, body: any, loading?: Ref, ) => Promise> = (user_group_id, body, loading) => { return post(`${prefix}/${user_group_id}/add_member`, body, {}, loading) } /** * 从用户组删除用户 */ const postRemoveMember: ( user_group_id: string, body: any, loading?: Ref, ) => Promise> = (user_group_id, body, loading) => { return post(`${prefix}/${user_group_id}/remove_member`, body, {}, loading) } /** * 获取用户组的成员列表 */ const getUserListByGroup: ( user_group_id: string, page: pageRequest, params ?: any, loading?: Ref, ) => Promise>> = (user_group_id, page, params, loading) => { return get( `${prefix}/${user_group_id}/user_list/${page.current_page}/${page.page_size}`, params ? params : undefined, loading, ) } export default { getUserGroup, postUserGroup, delUserGroup, postAddMember, postRemoveMember, getUserListByGroup } ================================================ FILE: ui/src/api/workspace/workspace.ts ================================================ import { Result } from '@/request/Result' import type { Ref } from 'vue' import { get, post, del } from '@/request/index' import type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem, } from '@/api/type/workspace' import type { pageRequest, PageList } from '@/api/type/common' const prefix = '/workspace' /** * 获取首页的工作空间下拉列表 */ const getWorkspaceListByUser: (loading?: Ref) => Promise> = ( loading, ) => { return get('/workspace/by_user', undefined, loading) } /** * 获取添加成员时的工作空间下拉列表 */ const getWorkspaceList: (loading?: Ref) => Promise[]>> = ( loading, ) => { return get('/workspace/current_user', undefined, loading) } /** * 获取工作空间列表 */ const getSystemWorkspaceList: (loading?: Ref) => Promise> = ( loading, ) => { return get(`${prefix}`, undefined, loading) } /** * 获取工作空间成员列表 */ const getWorkspaceMemberList: ( workspace_id: string, page: pageRequest, param: any, loading?: Ref, ) => Promise>> = (workspace_id, page, param, loading) => { return get( `${prefix}/${workspace_id}/user_list/${page.current_page}/${page.page_size}`, param, loading, ) } /** * 获取工作空间全部成员列表 */ const getAllMemberList: ( workspace_id: string | null, loading?: Ref, ) => Promise> = (workspace_id, loading) => { return get(`${prefix}/${workspace_id}/user_list`, undefined, loading) } /** * 新建工作空间成员 */ const CreateWorkspaceMember: ( workspace_id: string, data: CreateWorkspaceMemberParamsItem[], loading?: Ref, ) => Promise> = (workspace_id, data, loading) => { return post(`${prefix}/${workspace_id}/add_member`, data, undefined, loading) } /** * 删除工作空间成员 */ const deleteWorkspaceMember: ( workspace_id: string, user_relation_id: string, loading?: Ref, ) => Promise> = (workspace_id, user_relation_id, loading) => { return post(`${prefix}/${workspace_id}/remove_member/${user_relation_id}`, undefined, {}, loading) } /** * 获取添加成员时的角色下拉列表 */ const getWorkspaceRoleList: (loading?: Ref) => Promise[]>> = ( loading, ) => { return get('/role_list/current_user', undefined, loading) } export default { getWorkspaceList, getSystemWorkspaceList, getWorkspaceMemberList, getAllMemberList, CreateWorkspaceMember, deleteWorkspaceMember, getWorkspaceRoleList, getWorkspaceListByUser, } ================================================ FILE: ui/src/bus/index.ts ================================================ import mitt from "mitt"; const bus: any = {}; const emitter = mitt(); bus.on = emitter.on; bus.off = emitter.off; bus.emit = emitter.emit; export default bus; ================================================ FILE: ui/src/chat.ts ================================================ import '@/styles/index.scss' import ElementPlus from 'element-plus' import * as ElementPlusIcons from '@element-plus/icons-vue' import zhCn from 'element-plus/es/locale/lang/zh-cn' import enUs from 'element-plus/es/locale/lang/en' import zhTW from 'element-plus/es/locale/lang/zh-tw' import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import router from '@/router/chat' import i18n from '@/locales' import Components from '@/components' import directives from '@/directives' import { getDefaultWhiteList } from 'xss' import { config, XSSPlugin } from 'md-editor-v3' import screenfull from 'screenfull' import katex from 'katex' import 'katex/dist/katex.min.css' import Cropper from 'cropperjs' import mermaid from 'mermaid' import highlight from 'highlight.js' import 'highlight.js/styles/atom-one-dark.css' config({ editorExtensions: { highlight: { instance: highlight, }, screenfull: { instance: screenfull, }, katex: { instance: katex, }, cropper: { instance: Cropper, }, mermaid: { instance: mermaid, }, }, markdownItPlugins(plugins) { return [ ...plugins, { type: 'xss', plugin: XSSPlugin, options: { xss() { return { whiteList: Object.assign({}, getDefaultWhiteList(), { video: ['src', 'controls', 'width', 'height', 'preload', 'playsinline'], source: ['src', 'type'], input: ['class', 'disabled', 'type', 'checked'], iframe: [ 'class', 'width', 'height', 'src', 'title', 'border', 'frameborder', 'framespacing', 'allow', 'allowfullscreen', ], }), onTagAttr: (tag: string, name: any, value: any) => { if (tag === 'video') { // 禁止自动播放 if (name === 'autoplay') return '' // 限制 preload if (name === 'preload' && !['none', 'metadata'].includes(value)) { return 'preload="metadata"' } } return undefined }, } }, }, }, ] }, }) const app = createApp(App) app.use(createPinia()) for (const [key, component] of Object.entries(ElementPlusIcons)) { app.component(key, component) } const locale_map: any = { 'zh-CN': zhCn, 'zh-Hant': zhTW, 'en-US': enUs, } app.use(ElementPlus, { locale: locale_map[localStorage.getItem('MaxKB-locale') || navigator.language || 'en-US'], }) app.use(directives) app.use(router) app.use(i18n) app.use(Components) app.mount('#app') export { app } ================================================ FILE: ui/src/components/ai-chat/component/answer-content/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/chat-input-operate/TouchChat.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/chat-input-operate/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/control/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/knowledge-source-component/ExecutionDetailContent.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/knowledge-source-component/ParagraphDocumentContent.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/knowledge-source-component/ParagraphSourceContent.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/knowledge-source-component/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/ChatOperationButton.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/LogOperationButton.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/MobileVoteReasonDrawer.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/ShareOperationButton.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/VoteReasonContent.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/operation-button/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/prologue-content/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/question-content/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/transition-content/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/component/user-form/index.vue ================================================ ================================================ FILE: ui/src/components/ai-chat/index.scss ================================================ .ai-chat { --padding-left: 36px; height: 100%; display: flex; flex-direction: column; box-sizing: border-box; position: relative; color: var(--app-text-color); box-sizing: border-box; .back-bottom-button { position: absolute; right: 14px; top: -30px; z-index: 22; } &__content { padding-top: 0; box-sizing: border-box; .avatar { float: left; } .content { :deep(ol) { margin-left: 16px !important; } } } .video-stop-button { box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08); &:hover { background: #ffffff; } } .el-checkbox-group { font-size: inherit; line-height: inherit; } .is-selected { background: rgba(var(--el-text-color-primary-rgb), 0.08); } .mul-operation { position: fixed; bottom: 0; right: 0; padding: 16px 24px; box-sizing: border-box; background: #ffffff; z-index: 22; box-shadow: 0px -2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.08); } } .chat-width { max-width: 80%; margin: 0 auto; } @media only screen and (max-width: 1000px) { .chat-width { max-width: 100% !important; margin: 0 auto; } } @media only screen and (max-width: 768px) { .ai-chat { height: calc(100% - 116px) !important; } } .chat-mobile { .el-button.is-text:not(.is-disabled):hover { background: none; } } .chat-pc { &.openLeft { .mul-operation { margin-left: 281px; width: calc(100% - 281px); } } &.hideLeft { .mul-operation { margin-left: 65px; width: calc(100% - 65px); } } } ================================================ FILE: ui/src/components/ai-chat/index.vue ================================================ ================================================ FILE: ui/src/components/app-charts/components/BarCharts.vue ================================================ ================================================ FILE: ui/src/components/app-charts/components/LineCharts.vue ================================================ ================================================ FILE: ui/src/components/app-charts/index.vue ================================================ ================================================ FILE: ui/src/components/app-icon/AppIcon.vue ================================================ ================================================ FILE: ui/src/components/app-icon/KnowledgeIcon.vue ================================================ ================================================ FILE: ui/src/components/app-icon/ToolIcon.vue ================================================ ================================================ FILE: ui/src/components/app-icon/TriggerIcon.vue ================================================ ================================================ FILE: ui/src/components/app-icon/icons/about.ts ================================================ import { h } from 'vue' export default { 'app-github': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z', fill: 'currentColor', }), ], ), ]) }, }, 'app-trigger': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 18 18', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M9.16458 4.44856C9.09375 1.9795 7.06958 0 4.58333 0C2.05208 0 0 2.052 0 4.58314C0 7.06804 1.97708 9.09087 4.44458 9.1642L3.72792 7.37219C3.24691 7.22438 2.81234 6.95463 2.46643 6.58918C2.12052 6.22374 1.87505 5.77501 1.75388 5.28663C1.63271 4.79826 1.63995 4.28684 1.77492 3.80209C1.90988 3.31734 2.16797 2.87575 2.5241 2.52025C2.88022 2.16475 3.32227 1.90743 3.80727 1.77331C4.29227 1.63918 4.80372 1.63281 5.29191 1.75481C5.7801 1.87682 6.22842 2.12305 6.59329 2.46957C6.95816 2.81609 7.22717 3.25111 7.37417 3.73234L9.16458 4.44856Z', fill: 'currentColor', }), h('path', { d: 'M8.98125 17.1364C9.26292 17.8405 10.2629 17.8326 10.5333 17.1239L12.3442 12.3799L17.1263 10.5329C17.8325 10.26 17.8383 9.26295 17.1354 8.98171L5.35042 4.26774C4.67 3.99567 3.995 4.67106 4.26709 5.35103L8.98125 17.1364Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-help': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-user-manual': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M768 128H256a85.333333 85.333333 0 0 0-85.333333 85.333333v426.666667h512V64h85.333333v640a21.333333 21.333333 0 0 1-21.333333 21.333333H256a85.333333 85.333333 0 0 0-0.128 170.666667H832a21.333333 21.333333 0 0 0 21.333333-21.333333V341.333333h85.333334v597.333334a42.666667 42.666667 0 0 1-42.666667 42.666666H256c-94.293333 0-170.666667-76.16-170.666667-170.410666V213.248C85.333333 119.04 161.706667 42.666667 256 42.666667h469.333333a42.666667 42.666667 0 0 1 42.666667 42.666666v42.666667z', fill: 'currentColor', }), h('path', { d: 'M277.333333 768a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h469.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-469.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-pricing': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M354.261333 774.314667c19.626667 6.613333 24.106667 25.813333 9.941334 40.704-21.504 21.802667-151.552 39.808-190.165334 45.482666-7.381333 1.109333-14.165333 3.328-21.248-0.725333-10.197333-5.674667-11.904-15.616-9.941333-28.885333 5.205333-35.84 21.76-127.018667 47.061333-193.365334 2.261333-5.930667 9.685333-6.869333 15.616-6.4 12.288 0 18.901333 5.461333 23.381334 18.005334 17.066667 48.981333 48.512 86.144 93.909333 111.445333 9.941333 5.461333 20.565333 9.941333 31.445333 13.738667zM902.698667 127.146667c2.346667 14.549333 3.968 28.842667 3.754666 43.605333-2.133333 73.386667-18.176 143.957333-44.8 212.394667-31.872 81.834667-78.549333 153.770667-143.914666 213.333333a18.133333 18.133333 0 0 0-6.4 16.853333c2.389333 22.016 4.053333 65.408 6.4 87.466667 5.205333 51.328-12.757333 93.269333-54.485334 123.050667-44.8 31.872-91.306667 61.44-137.258666 91.434666-29.013333 18.773333-64.64 1.621333-67.968-32.597333-3.754667-39.381333-6.613333-100.096-9.429334-139.477333-1.450667-19.925333-0.938667-19.925333-20.053333-22.485334-51.2-6.570667-91.050667-30.72-118.4-74.325333-14.165333-22.485333-21.248-47.36-23.594667-73.386667-0.725333-7.978667-4.010667-9.813333-11.349333-10.325333-41.258667-2.090667-103.893333-4.693333-145.152-7.722667-34.218667-2.56-51.669333-38.442667-33.28-68.437333 12.757333-21.12 26.453333-41.728 39.893333-62.592 14.378667-22.528 28.501333-44.8 42.922667-67.285333 26.410667-41.002667 64.384-63.061333 112.981333-63.530667 27.818667-0.213333 77.013333 4.693333 104.832 7.722667 5.418667 0.469333 9.216-0.213333 12.714667-4.181334 64.64-71.765333 144.384-120.277333 234.965333-152.618666a675.584 675.584 0 0 1 157.824-35.84c27.349333-2.858667 54.698667-3.797333 81.834667 1.152 14.848 2.816 15.616 3.498667 17.92 17.792z m-90.965334 65.92c-47.232 4.906667-93.184 15.36-137.941333 31.36-82.133333 29.312-148.138667 71.466667-199.850667 128.853333-23.381333 26.325333-53.248 35.242667-85.845333 32.426667-7.936-0.853333-15.829333-1.877333-23.765333-2.901334a634.453333 634.453333 0 0 0-70.954667-4.352c-19.029333 0.170667-30.72 6.826667-41.898667 24.149334l-21.333333 33.450666-24.618667 38.485334c17.792 1.066667 35.712 2.090667 53.802667 2.986666 20.48 1.322667 59.733333 6.186667 78.634667 21.973334 22.144 18.432 31.402667 41.514667 33.536 65.834666 1.365333 14.72 4.906667 26.197333 10.88 35.669334 13.269333 21.12 30.08 31.573333 57.6 35.114666l7.296 1.024c8.192 1.28 14.72 2.688 22.741333 5.546667 15.829333 5.632 30.421333 15.104 42.154667 29.866667 10.453333 13.141333 15.914667 48 18.773333 62.208 1.28 6.4 5.248 58.026667 6.229333 71.381333a2236.16 2236.16 0 0 0 76.501334-51.754667c16.170667-11.52 21.333333-23.338667 19.2-44.501333-1.024-9.813333-5.589333-80.256-6.4-87.722667a103.125333 103.125333 0 0 1 33.792-88.746666c53.546667-48.853333 93.696-108.885333 121.856-181.248 21.12-54.186667 33.749333-107.434667 37.802666-159.872a335.018667 335.018667 0 0 0-8.192 0.768zM672 405.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/application.ts ================================================ import { h } from 'vue' export default { 'app-create-chat': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15H1.66667C1.29848 15 1 14.7015 1 14.3333V8C1 4.13401 4.13401 1 8 1ZM2.33333 13.6667H8C11.1296 13.6667 13.6667 11.1296 13.6667 7.99998C13.6667 4.87037 11.1296 2.33332 8 2.33332C4.87039 2.33332 2.33333 4.87037 2.33333 7.99998V13.6667Z', fill: 'currentColor', }), h('path', { d: 'M7.66667 5C7.48257 5 7.33333 5.14924 7.33333 5.33333V7.33333H5.33333C5.14924 7.33333 5 7.48257 5 7.66667V8.33333C5 8.51743 5.14924 8.66667 5.33333 8.66667H7.33333V10.6667C7.33333 10.8508 7.48257 11 7.66667 11H8.33333C8.51743 11 8.66667 10.8508 8.66667 10.6667V8.66667H10.6667C10.8508 8.66667 11 8.51743 11 8.33333V7.66667C11 7.48257 10.8508 7.33333 10.6667 7.33333H8.66667V5.33333C8.66667 5.14924 8.51743 5 8.33333 5H7.66667Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-access': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M490.368 48.554667a42.666667 42.666667 0 0 1 43.264 0l362.666667 213.333333A42.666667 42.666667 0 0 1 917.333333 298.666667v426.666666a42.666667 42.666667 0 0 1-21.034666 36.778667l-362.666667 213.333333a42.666667 42.666667 0 0 1-43.264 0l-362.666667-213.333333A42.666667 42.666667 0 0 1 106.666667 725.333333V298.666667a42.666667 42.666667 0 0 1 21.034666-36.778667l362.666667-213.333333zM192 323.072v377.856L512 889.173333l320-188.245333V323.072L512 134.826667 192 323.072z', fill: 'currentColor', }), h('path', { d: 'M705.194667 441.472a42.666667 42.666667 0 1 0-45.226667-72.362667l-148.096 92.586667L363.946667 369.066667a42.666667 42.666667 0 1 0-45.312 72.362666L469.333333 535.722667V704a42.666667 42.666667 0 1 0 85.333334 0v-168.448l150.528-94.08z', fill: 'currentColor', }), ], ), ]) }, }, 'app-access-active': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M533.632 48.554667a42.666667 42.666667 0 0 0-43.264 0l-362.666667 213.333333A42.666667 42.666667 0 0 0 106.666667 298.666667v426.666666a42.666667 42.666667 0 0 0 21.034666 36.778667l362.666667 213.333333a42.666667 42.666667 0 0 0 43.264 0l362.666667-213.333333A42.666667 42.666667 0 0 0 917.333333 725.333333V298.666667a42.666667 42.666667 0 0 0-21.034666-36.778667l-362.666667-213.333333z m185.130667 334.08a42.666667 42.666667 0 0 1-13.568 58.837333L554.666667 535.552V704a42.666667 42.666667 0 1 1-85.333334 0v-168.277333l-150.613333-94.293334a42.666667 42.666667 0 0 1 45.226667-72.32l147.925333 92.586667 148.053333-92.586667a42.666667 42.666667 0 0 1 58.837334 13.568z', fill: 'currentColor', }), ], ), ]) }, }, 'app-user': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 24 24', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M15 13H9C6.23858 13 3 14.9314 3 18.4V21.1C3 21.597 3.44772 22 4 22H20C20.5523 22 21 21.597 21 21.1V18.4C21 14.9285 17.7614 13 15 13Z', fill: 'currentColor', }), h('path', { d: 'M7 6.99997C7 9.76139 9.23858 12 12 12C14.7614 12 17 9.76139 17 6.99997C17 4.23855 14.7614 1.99997 12 1.99997C9.23858 1.99997 7 4.23855 7 6.99997Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-question': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 24 24', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M12.7071 22.2009L17 18.5111H21.5C22.0523 18.5111 22.5 18.0539 22.5 17.4899V2.52112C22.5 1.95715 22.0523 1.49997 21.5 1.49997H2C1.44772 1.49997 1 1.95715 1 2.52112V17.4899C1 18.0539 1.44772 18.5111 2 18.5111H7L11.2929 22.2009C11.6834 22.5997 12.3166 22.5997 12.7071 22.2009ZM6.5 8.49997H7.5C8.05228 8.49997 8.5 8.94768 8.5 9.49997V10.5C8.5 11.0523 8.05228 11.5 7.5 11.5H6.5C5.94772 11.5 5.5 11.0523 5.5 10.5V9.49997C5.5 8.94768 5.94772 8.49997 6.5 8.49997ZM10.5 9.49997C10.5 8.94768 10.9477 8.49997 11.5 8.49997H12.5C13.0523 8.49997 13.5 8.94768 13.5 9.49997V10.5C13.5 11.0523 13.0523 11.5 12.5 11.5H11.5C10.9477 11.5 10.5 11.0523 10.5 10.5V9.49997ZM16.5 8.49997H17.5C18.0523 8.49997 18.5 8.94768 18.5 9.49997V10.5C18.5 11.0523 18.0523 11.5 17.5 11.5H16.5C15.9477 11.5 15.5 11.0523 15.5 10.5V9.49997C15.5 8.94768 15.9477 8.49997 16.5 8.49997Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-tokens': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 24 24', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M15.6 2.39996C12.288 2.39996 9.60002 5.08796 9.60002 8.39996C9.60002 9.11996 9.74402 9.79196 9.97202 10.428L2.47325 17.9267C2.42636 17.9736 2.40002 18.0372 2.40002 18.1035V21.1C2.40002 21.3761 2.62388 21.6 2.90002 21.6H4.30002C4.57617 21.6 4.80002 21.3761 4.80002 21.1V20.4H6.70003C6.97617 20.4 7.20002 20.1761 7.20002 19.9V18H8.40002L10.8 15.6H12L13.572 14.028C14.208 14.256 14.88 14.4 15.6 14.4C18.912 14.4 21.6 11.712 21.6 8.39996C21.6 5.08796 18.912 2.39996 15.6 2.39996ZM17.4 8.39996C16.404 8.39996 15.6 7.59596 15.6 6.59996C15.6 5.60396 16.404 4.79996 17.4 4.79996C18.396 4.79996 19.2 5.60396 19.2 6.59996C19.2 7.59596 18.396 8.39996 17.4 8.39996Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-user-stars': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 24 24', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M12 23C18.0751 23 23 18.0751 23 12C23 5.92484 18.0751 0.999969 12 0.999969C5.92487 0.999969 1 5.92484 1 12C1 18.0751 5.92487 23 12 23ZM8.5 10.5C7.67157 10.5 7 9.8284 7 8.99997C7 8.17154 7.67157 7.49997 8.5 7.49997C9.32843 7.49997 10 8.17154 10 8.99997C10 9.8284 9.32843 10.5 8.5 10.5ZM17 8.99997C17 9.8284 16.3284 10.5 15.5 10.5C14.6716 10.5 14 9.8284 14 8.99997C14 8.17154 14.6716 7.49997 15.5 7.49997C16.3284 7.49997 17 8.17154 17 8.99997ZM16.9779 13.4994C16.7521 16.0264 14.8169 18 12 18C9.18312 18 7.24789 16.0264 7.02213 13.4994C6.99756 13.2244 7.22386 13 7.5 13H16.5C16.7761 13 17.0024 13.2244 16.9779 13.4994Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-like': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.00518 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00004 5.33317 1.98014 14.6874 2.00518 14.6608ZM9.70096 5.28984H12.5717C14.5687 5.28984 15.0274 7.05264 14.5687 8.37353L12.5717 13.6308C12.4029 14.2423 11.8409 14.6665 11.1995 14.6665H3.33882C3.154 14.6665 3.00418 14.5167 3.00418 14.3319V5.62448C3.00418 5.43966 3.154 5.28984 3.33882 5.28984H4.02656C4.24449 5.28984 4.44877 5.18374 4.5741 5.00545L7.35254 1.05296C7.5406 0.753754 8.04824 0.52438 8.5893 0.770777C9.40089 1.14037 10.3724 1.94718 10.3724 3.28394C10.3724 3.78809 10.1486 4.45673 9.70096 5.28984ZM12.5717 6.62841H7.46215L8.52183 4.65626C8.87422 4.00045 9.03388 3.52351 9.03388 3.28394C9.03388 2.89556 8.9524 2.45627 8.25544 2.09612L5.26934 6.34402C5.14401 6.5223 4.93973 6.62841 4.72181 6.62841H4.34275V13.3279H11.1995C11.2411 13.3279 11.2734 13.3035 11.2813 13.2747L11.298 13.2142L13.3098 7.91815C13.5743 7.13902 13.3105 6.62841 12.5717 6.62841Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-like-color': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.00497 14.6608H2.00518C2.00511 14.6609 2.00504 14.6609 2.00497 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00006 5.33305 1.98026 14.6344 2.00497 14.6608Z', fill: '#FFC60A', }), h('path', { d: 'M12.5717 5.28984H9.70096C10.1486 4.45673 10.3724 3.78809 10.3724 3.28394C10.3724 1.94718 9.40089 1.14037 8.5893 0.770777C8.04824 0.52438 7.5406 0.753754 7.35254 1.05296L4.5741 5.00545C4.44877 5.18374 4.24449 5.28984 4.02656 5.28984H3.33882C3.154 5.28984 3.00418 5.43966 3.00418 5.62448V14.3319C3.00418 14.5167 3.154 14.6665 3.33882 14.6665H11.1995C11.8409 14.6665 12.4029 14.2423 12.5717 13.6308L14.5687 8.37353C15.0274 7.05264 14.5687 5.28984 12.5717 5.28984Z', fill: '#FFC60A', }), ], ), ]) }, }, 'app-oppose': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.00518 1.28008H0.666616C0.666616 1.33341 0.666504 10.6667 0.666616 10.65H2.00518C1.99984 10.6667 1.99984 1.33341 2.00518 1.28008ZM9.70097 10.6511H12.5717C14.5687 10.6511 15.0274 8.88828 14.5687 7.56739L12.5717 2.3101C12.4029 1.69862 11.8409 1.27441 11.1996 1.27441H3.33883C3.15401 1.27441 3.00418 1.42424 3.00418 1.60906V10.3164C3.00418 10.5013 3.15401 10.6511 3.33883 10.6511H4.02656C4.24449 10.6511 4.44877 10.7572 4.5741 10.9355L7.35254 14.888C7.5406 15.1872 8.04825 15.4165 8.58931 15.1701C9.40089 14.8005 10.3724 13.9937 10.3724 12.657C10.3724 12.1528 10.1486 11.4842 9.70097 10.6511ZM12.5717 9.31251H7.46216L8.52184 11.2847C8.87422 11.9405 9.03388 12.4174 9.03388 12.657C9.03388 13.0454 8.95241 13.4846 8.25545 13.8448L5.26935 9.5969C5.14402 9.41861 4.93974 9.31251 4.72181 9.31251H4.34275V2.61298H11.1996C11.2411 2.61298 11.2734 2.63737 11.2813 2.6662L11.298 2.72673L13.3098 8.02277C13.5743 8.8019 13.3105 9.31251 12.5717 9.31251Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-oppose-color': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M9.70106 10.7102H12.5718C14.5688 10.7102 15.0275 8.94736 14.5688 7.62647L12.5718 2.36918C12.403 1.7577 11.841 1.3335 11.1996 1.3335H3.33891C3.1541 1.3335 3.00427 1.48332 3.00427 1.66814V10.3755C3.00427 10.5603 3.1541 10.7102 3.33891 10.7102H4.02665C4.24458 10.7102 4.44886 10.8163 4.57419 10.9945L7.35263 14.947C7.54069 15.2462 8.04834 15.4756 8.58939 15.2292C9.40098 14.8596 10.3725 14.0528 10.3725 12.7161C10.3725 12.2119 10.1487 11.5433 9.70106 10.7102Z', fill: '#F54A45', }), h('path', { d: 'M2.00004 1.3335H0.661473C0.661473 1.3335 0.660982 10.7764 0.661473 10.7035H2.00001C1.99469 10.6868 1.9947 1.38674 2.00004 1.3335Z', fill: '#F54A45', }), ], ), ]) }, }, 'app-debug-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 14 14', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.63333 1.82346C2.81847 1.72056 3.04484 1.72611 3.22472 1.83795L10.8081 6.55299C10.9793 6.65945 11.0834 6.84677 11.0834 7.04838C11.0834 7.24999 10.9793 7.43731 10.8081 7.54376L3.22472 12.2588C3.04484 12.3707 2.81847 12.3762 2.63333 12.2733C2.44819 12.1704 2.33337 11.9752 2.33337 11.7634V2.33333C2.33337 2.12152 2.44819 1.92635 2.63333 1.82346ZM3.50004 3.38293V10.7138L9.39529 7.04838L3.50004 3.38293Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-save-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 14 14', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M1.16666 2.53734C1.16666 1.78025 1.7804 1.1665 2.53749 1.1665H11.4625C12.2196 1.1665 12.8333 1.78025 12.8333 2.53734V11.4623C12.8333 12.2194 12.2196 12.8332 11.4625 12.8332H2.53749C1.7804 12.8332 1.16666 12.2194 1.16666 11.4623V2.53734ZM2.53749 2.33317C2.42473 2.33317 2.33332 2.42458 2.33332 2.53734V11.4623C2.33332 11.5751 2.42473 11.6665 2.53749 11.6665H11.4625C11.5753 11.6665 11.6667 11.5751 11.6667 11.4623V2.53734C11.6667 2.42457 11.5753 2.33317 11.4625 2.33317H2.53749Z', fill: 'currentColor', }), h('path', { d: 'M3.79166 1.74984C3.79166 1.42767 4.05282 1.1665 4.37499 1.1665H9.33332C9.65549 1.1665 9.91666 1.42767 9.91666 1.74984V6.99984C9.91666 7.322 9.65549 7.58317 9.33332 7.58317H4.37499C4.05282 7.58317 3.79166 7.322 3.79166 6.99984V1.74984ZM4.95832 2.33317V6.4165H8.74999V2.33317H4.95832Z', fill: 'currentColor', }), h('path', { d: 'M7.58333 3.2085C7.9055 3.2085 8.16667 3.46966 8.16667 3.79183V4.9585C8.16667 5.28066 7.9055 5.54183 7.58333 5.54183C7.26117 5.54183 7 5.28066 7 4.9585V3.79183C7 3.46966 7.26117 3.2085 7.58333 3.2085Z', fill: 'currentColor', }), h('path', { d: 'M2.62415 1.74984C2.62415 1.42767 2.88531 1.1665 3.20748 1.1665H10.4996C10.8217 1.1665 11.0829 1.42767 11.0829 1.74984C11.0829 2.072 10.8217 2.33317 10.4996 2.33317H3.20748C2.88531 2.33317 2.62415 2.072 2.62415 1.74984Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-history-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M18.6667 10.0001C18.6667 14.6025 14.9358 18.3334 10.3334 18.3334C7.68359 18.3334 5.32266 17.0967 3.79633 15.1689L5.12054 14.1563C6.3421 15.6864 8.22325 16.6667 10.3334 16.6667C14.0153 16.6667 17 13.682 17 10.0001C17 6.31818 14.0153 3.33341 10.3334 3.33341C7.03005 3.33341 4.28786 5.73596 3.75889 8.88897H4.3469C4.70187 8.88897 4.9136 9.28459 4.7167 9.57995L3.32493 11.6676C3.14901 11.9315 2.76125 11.9315 2.58533 11.6676L1.19356 9.57995C0.996651 9.28459 1.20838 8.88897 1.56336 8.88897H2.07347C2.61669 4.8119 6.10774 1.66675 10.3334 1.66675C14.9358 1.66675 18.6667 5.39771 18.6667 10.0001Z', fill: 'currentColor', }), h('path', { d: 'M10.8334 9.7223V7.11119C10.8334 6.86573 10.6344 6.66675 10.3889 6.66675H9.61115C9.36569 6.66675 9.16671 6.86573 9.16671 7.11119V10.9445C9.16671 11.19 9.36569 11.389 9.61115 11.389H13.1667C13.4122 11.389 13.6112 11.19 13.6112 10.9445V10.1667C13.6112 9.92129 13.4122 9.7223 13.1667 9.7223H10.8334Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-fitview': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M128 85.333333h192a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H170.666667v149.333333a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V128a42.666667 42.666667 0 0 1 42.666667-42.666667z m768 853.333334h-192a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H853.333333v-149.333333a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V896a42.666667 42.666667 0 0 1-42.666667 42.666667zM85.333333 896v-192a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V853.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H128a42.666667 42.666667 0 0 1-42.666667-42.666667zM938.666667 128v192a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V170.666667h-149.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H896a42.666667 42.666667 0 0 1 42.666667 42.666667z', fill: 'currentColor', }), h('path', { d: 'M512 512m-170.666667 0a170.666667 170.666667 0 1 0 341.333334 0 170.666667 170.666667 0 1 0-341.333334 0Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-retract': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M5.44661 0.747985C5.55509 0.639506 5.73097 0.639506 5.83945 0.747985L8.00004 2.90858L10.1606 0.748004C10.2691 0.639525 10.445 0.639525 10.5534 0.748004L11.1034 1.29798C11.2119 1.40645 11.2119 1.58233 11.1034 1.69081L8.7488 4.04544L8.74644 4.04782L8.19647 4.59779C8.16892 4.62534 8.13703 4.64589 8.10299 4.65945C8.003 4.6993 7.88453 4.67875 7.80359 4.59781L7.25362 4.04784L7.25003 4.04419L4.89664 1.69079C4.78816 1.58232 4.78816 1.40644 4.89664 1.29796L5.44661 0.747985Z', fill: 'currentColor', }), h('path', { d: 'M1.99999 5.82774C1.63181 5.82774 1.33333 6.12622 1.33333 6.49441V9.16107C1.33333 9.52926 1.63181 9.82774 2 9.82774H14C14.3682 9.82774 14.6667 9.52926 14.6667 9.16107V6.49441C14.6667 6.12622 14.3682 5.82774 14 5.82774H1.99999ZM13.3333 7.16108V8.49441H2.66666V7.16108H13.3333Z', fill: 'currentColor', }), h('path', { d: 'M10.1605 14.9075C10.269 15.016 10.4449 15.016 10.5534 14.9075L11.1033 14.3575C11.2118 14.249 11.2118 14.0732 11.1033 13.9647L8.75 11.6113L8.74637 11.6076L8.1964 11.0577C8.11546 10.9767 7.99699 10.9562 7.897 10.996C7.86296 11.0096 7.83107 11.0301 7.80352 11.0577L7.25354 11.6077L7.25117 11.6101L4.89657 13.9647C4.78809 14.0731 4.78809 14.249 4.89657 14.3575L5.44654 14.9075C5.55502 15.016 5.7309 15.016 5.83938 14.9075L7.99995 12.7469L10.1605 14.9075Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-extend': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M10.5534 5.07974C10.4449 5.18822 10.269 5.18822 10.1605 5.07974L7.99992 2.91915L5.83935 5.07972C5.73087 5.1882 5.555 5.1882 5.44652 5.07972L4.89654 4.52975C4.78807 4.42127 4.78807 4.24539 4.89654 4.13691L7.25117 1.78229L7.25352 1.77991L7.80349 1.22994C7.83019 1.20324 7.86098 1.18311 7.89384 1.16955C7.99448 1.12801 8.11459 1.14813 8.19638 1.22992L8.74635 1.77989L8.74998 1.78359L11.1033 4.13693C11.2118 4.24541 11.2118 4.42129 11.1033 4.52977L10.5534 5.07974Z', fill: 'currentColor', }), h('path', { d: 'M5.83943 10.9202C5.73095 10.8118 5.55507 10.8118 5.44659 10.9202L4.89662 11.4702C4.78814 11.5787 4.78814 11.7546 4.89662 11.863L7.24997 14.2164L7.25359 14.2201L7.80357 14.7701C7.8862 14.8527 8.00795 14.8724 8.10922 14.8291C8.14091 14.8156 8.17059 14.7959 8.19645 14.77L8.74642 14.2201L8.74873 14.2177L11.1034 11.8631C11.2119 11.7546 11.2119 11.5787 11.1034 11.4702L10.5534 10.9202C10.4449 10.8118 10.2691 10.8118 10.1606 10.9202L8.00002 13.0808L5.83943 10.9202Z', fill: 'currentColor', }), h('path', { d: 'M2.00004 6C1.63185 6 1.33337 6.29848 1.33337 6.66667V9.33333C1.33337 9.70152 1.63185 10 2.00004 10H14C14.3682 10 14.6667 9.70152 14.6667 9.33333V6.66667C14.6667 6.29848 14.3682 6 14 6H2.00004ZM13.3334 7.33333V8.66667H2.66671V7.33333H13.3334Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-beautify': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M739.6864 689.92l4.2496 3.584 136.4992 135.936a34.1504 34.1504 0 0 1-43.9296 51.968l-4.1984-3.584-136.5504-135.936a34.1504 34.1504 0 0 1 43.9296-51.968zM663.4496 151.552a34.1504 34.1504 0 0 1 51.2512 30.464l-5.9392 216.6272 156.4672 146.1248a34.1504 34.1504 0 0 1-8.6528 55.808l-4.8128 1.792-202.8032 61.0816-87.4496 197.12a34.1504 34.1504 0 0 1-56.32 9.216l-3.2768-4.096-119.5008-178.432-209.9712-24.064a34.1504 34.1504 0 0 1-26.1632-50.176l2.7648-4.3008 129.28-171.7248-42.5472-212.3776a34.1504 34.1504 0 0 1 40.448-40.1408l4.6592 1.3312 198.912 72.3456z m-18.6368 89.7536l-144.5376 83.968a34.1504 34.1504 0 0 1-28.8256 2.56L314.5728 270.592l33.792 167.8848c1.4848 7.68 0.3584 15.5136-3.1744 22.3232l-3.072 4.9152-102.656 136.2944 166.4 19.1488c8.2944 0.9216 15.872 4.864 21.4016 10.9568l3.072 3.9424 93.8496 140.032 68.7104-154.7776a34.1504 34.1504 0 0 1 16.7936-17.0496l4.608-1.792 160.9216-48.4864-124.2624-116.0192a34.1504 34.1504 0 0 1-10.4448-20.0704l-0.3584-5.7856 4.6592-170.9056z', fill: 'currentColor', }), ], ), ]) }, }, 'app-chat-record': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M11.3333 7.33334C11.3333 6.96515 11.6318 6.66667 12 6.66667H14.6667C15.0349 6.66667 15.3333 6.96515 15.3333 7.33334V12.6667C15.3333 13.0349 15.0349 13.3333 14.6667 13.3333H13.2761L12.4714 14.1381C12.2111 14.3984 11.7889 14.3984 11.5286 14.1381L10.7239 13.3333H7.33334C6.96515 13.3333 6.66667 13.0349 6.66667 12.6667V10C6.66667 9.63182 6.96515 9.33334 7.33334 9.33334H11.3333V7.33334ZM12.6667 8.00001V10C12.6667 10.3682 12.3682 10.6667 12 10.6667H8.00001V12H11C11.1768 12 11.3464 12.0702 11.4714 12.1953L12 12.7239L12.5286 12.1953C12.6536 12.0702 12.8232 12 13 12H14V8.00001H12.6667Z', fill: 'currentColor', }), h('path', { d: 'M1.33334 1.33333C0.965149 1.33333 0.666672 1.63181 0.666672 1.99999V10C0.666672 10.3682 0.965149 10.6667 1.33334 10.6667H2.72386L3.86193 11.8047C4.12228 12.0651 4.54439 12.0651 4.80474 11.8047L5.94281 10.6667H12C12.3682 10.6667 12.6667 10.3682 12.6667 10V1.99999C12.6667 1.63181 12.3682 1.33333 12 1.33333H1.33334ZM4.66667 5.99999C4.66667 6.36818 4.36819 6.66666 4.00001 6.66666C3.63182 6.66666 3.33334 6.36818 3.33334 5.99999C3.33334 5.6318 3.63182 5.33333 4.00001 5.33333C4.36819 5.33333 4.66667 5.6318 4.66667 5.99999ZM7.33334 5.99999C7.33334 6.36818 7.03486 6.66666 6.66667 6.66666C6.29848 6.66666 6 6.36818 6 5.99999C6 5.6318 6.29848 5.33333 6.66667 5.33333C7.03486 5.33333 7.33334 5.6318 7.33334 5.99999ZM10 5.99999C10 6.36818 9.70153 6.66666 9.33334 6.66666C8.96515 6.66666 8.66667 6.36818 8.66667 5.99999C8.66667 5.6318 8.96515 5.33333 9.33334 5.33333C9.70153 5.33333 10 5.6318 10 5.99999Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-video-play': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m469.333333-384c0 259.2-210.133333 469.333333-469.333333 469.333333S42.666667 771.2 42.666667 512 252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333z', fill: 'currentColor', }), h('path', { d: 'M686.890667 539.776l-253.141334 159.274667a32.298667 32.298667 0 0 1-44.8-10.453334 32.896 32.896 0 0 1-4.949333-17.322666V352.768a32.64 32.64 0 0 1 32.512-32.768c6.101333 0 12.074667 1.706667 17.28 4.992l253.098667 159.232a32.853333 32.853333 0 0 1 0 55.552z', fill: 'currentColor', }), ], ), ]) }, }, 'app-video-pause': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M405.333333 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v298.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333334v-298.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-42.666667zM576 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v298.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333334v-298.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-42.666667z', fill: 'currentColor', }), h('path', { d: 'M512 42.666667C252.8 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667zM128 512a384 384 0 1 1 768 0 384 384 0 0 1-768 0z', fill: 'currentColor', }), ], ), ]) }, }, 'app-video-stop': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M981.333333 512c0 259.2-210.133333 469.333333-469.333333 469.333333S42.666667 771.2 42.666667 512 252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333z m-85.333333 0a384 384 0 1 0-768 0 384 384 0 0 0 768 0zM384 341.333333h256c23.466667 0 42.666667 19.072 42.666667 42.666667v256c0 23.552-19.2 42.666667-42.666667 42.666667H384c-23.466667 0-42.666667-19.114667-42.666667-42.666667V384c0-23.594667 19.2-42.666667 42.666667-42.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-chat': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 64c247.424 0 448 200.576 448 448S759.424 960 512 960H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V512C64 264.576 264.576 64 512 64z m-362.666667 810.666667H512A362.666667 362.666667 0 1 0 149.333333 512v362.666667z m170.666667-298.666667h213.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-213.333333A21.333333 21.333333 0 0 1 298.666667 640v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z m0-170.666667h384a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-384A21.333333 21.333333 0 0 1 298.666667 469.333333v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-reference-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M121.216 714.368c-7.082667-17.493333-7.466667-83.413333-7.424-104.32 0.341333-142.72 34.048-256.426667 88.32-330.112C262.4 198.229333 351.701333 161.024 460.8 172.8c7.893333 0.853333 11.946667 7.338667 10.581333 16.981333l-7.381333 51.285334c-1.749333 12.202667-9.813333 12.885333-17.621333 12.202666-138.709333-11.946667-232.576 84.053333-245.76 296.704a165.632 165.632 0 0 1 83.754666-22.528c91.050667 0 164.906667 72.96 164.906667 162.944C449.28 780.373333 375.466667 853.333333 284.373333 853.333333c-82.858667 0-151.424-60.330667-163.157333-138.965333z m438.570667 0c-7.082667-17.493333-7.509333-83.413333-7.466667-104.32 0.426667-142.72 34.090667-256.426667 88.405333-330.112 60.202667-81.706667 149.504-118.912 258.645334-107.136 7.893333 0.853333 11.946667 7.338667 10.581333 16.981333l-7.381333 51.285334c-1.749333 12.202667-9.813333 12.885333-17.621334 12.202666-138.752-11.946667-232.576 84.053333-245.76 296.704a165.632 165.632 0 0 1 83.712-22.528c91.093333 0 164.906667 72.96 164.906667 162.944 0 90.026667-73.813333 162.944-164.906667 162.944-82.773333 0-151.381333-60.330667-163.114666-138.965333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-quote': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M800.768 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344-155.648 116.736-356.352 317.44-356.352 573.44v20.48c0 122.88 100.352 223.232 223.232 223.232S1024 825.344 1024 702.464c0-124.928-100.352-225.28-223.232-225.28zM223.232 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344C200.704 225.28 0 425.984 0 681.984v20.48c0 122.88 100.352 223.232 223.232 223.232s223.232-100.352 223.232-223.232c0-124.928-100.352-225.28-223.232-225.28z', fill: 'currentColor', }), ], ), ]) }, }, 'app-mobile-open-history': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 21 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M3.01237 4.16663H17.179C17.4568 4.16663 17.5957 4.30551 17.5957 4.58329V5.41663C17.5957 5.6944 17.4568 5.83329 17.179 5.83329H3.01237C2.73459 5.83329 2.5957 5.6944 2.5957 5.41663V4.58329C2.5957 4.30551 2.73459 4.16663 3.01237 4.16663Z', fill: 'currentColor', }), h('path', { d: 'M3.01237 9.16663H17.179C17.4568 9.16663 17.5957 9.30552 17.5957 9.5833V10.4166C17.5957 10.6944 17.4568 10.8333 17.179 10.8333H3.01237C2.73459 10.8333 2.5957 10.6944 2.5957 10.4166V9.5833C2.5957 9.30552 2.73459 9.16663 3.01237 9.16663Z', fill: 'currentColor', }), h('path', { d: 'M3.01237 14.1667H17.179C17.4568 14.1667 17.5957 14.3056 17.5957 14.5833V15.4167C17.5957 15.6944 17.4568 15.8333 17.179 15.8333H3.01237C2.73459 15.8333 2.5957 15.6944 2.5957 15.4167V14.5833C2.5957 14.3056 2.73459 14.1667 3.01237 14.1667Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-keyboard': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M373.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM320 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM565.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM512 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM757.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM704 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM362.666667 661.333333a42.666667 42.666667 0 1 0 0 85.333334h298.666666a42.666667 42.666667 0 1 0 0-85.333334h-298.666666z', fill: 'currentColor', }), h('path', { d: 'M512 42.666667C252.8 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667zM128 512a384 384 0 1 1 768 0 384 384 0 0 1-768 0z', fill: 'currentColor', }), ], ), ]) }, }, 'app-pdf-export': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M3.33366 5.83342V16.6667H16.667V10.8334H18.3337V17.5001C18.3337 17.9603 17.9606 18.3334 17.5003 18.3334H2.50033C2.04009 18.3334 1.66699 17.9603 1.66699 17.5001V5.00008C1.66699 4.53984 2.04009 4.16675 2.50033 4.16675H9.16699V5.83342H3.33366Z', fill: 'currentColor', }), h('path', { d: 'M18.3335 2.50008V8.33342H16.6668V4.51175L11.6876 9.49091C11.6095 9.56903 11.5035 9.61291 11.393 9.61291C11.2825 9.61291 11.1766 9.56903 11.0984 9.49091L10.5093 8.90175C10.4312 8.82361 10.3873 8.71765 10.3873 8.60716C10.3873 8.49668 10.4312 8.39072 10.5093 8.31258L15.4884 3.33341H11.6668V1.66675H17.5001C17.7211 1.66675 17.9331 1.75455 18.0894 1.91083C18.2457 2.06711 18.3335 2.27907 18.3335 2.50008Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-clock': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M469.333333 320a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V469.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333334v-213.333333z', fill: 'currentColor', }), h('path', { d: 'M512 981.333333c259.2 0 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333z m0-85.333333a384 384 0 1 1 0-768 384 384 0 0 1 0 768z', fill: 'currentColor', }), ], ), ]) }, }, 'app-generate-star': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M384 832c-12.8 0-25.6-8.533333-29.866667-21.333333l-34.133333-119.466667c-17.066667-55.466667-55.466667-93.866667-110.933333-110.933333L85.333333 541.866667c-12.8-4.266667-21.333333-17.066667-21.333333-29.866667 0-12.8 8.533333-25.6 21.333333-29.866667l119.466667-34.133333c55.466667-17.066667 93.866667-55.466667 110.933333-110.933333L354.133333 213.333333c4.266667-12.8 17.066667-21.333333 29.866667-21.333333 12.8 0 25.6 8.533333 29.866667 21.333333l34.133333 119.466667c17.066667 55.466667 55.466667 93.866667 110.933333 110.933333l119.466667 34.133334c12.8 4.266667 21.333333 17.066667 21.333333 29.866666 0 12.8-8.533333 25.6-21.333333 29.866667l-119.466667 34.133333c-55.466667 17.066667-93.866667 55.466667-110.933333 110.933334l-34.133333 128c-4.266667 12.8-17.066667 21.333333-29.866667 21.333333z m384-384c-12.8 0-25.6-8.533333-29.866667-25.6l-12.8-42.666667c-8.533333-38.4-42.666667-72.533333-81.066666-81.066666l-42.666667-12.8c-12.8-4.266667-25.6-17.066667-25.6-29.866667 0-12.8 8.533333-25.6 25.6-29.866667l42.666667-12.8c38.4-8.533333 72.533333-42.666667 81.066666-81.066666l12.8-42.666667c4.266667-12.8 17.066667-25.6 29.866667-25.6 12.8 0 25.6 8.533333 29.866667 25.6l12.8 42.666667c8.533333 38.4 42.666667 72.533333 81.066666 81.066666l42.666667 12.8c12.8 4.266667 25.6 17.066667 25.6 29.866667 0 12.8-8.533333 25.6-25.6 29.866667l-42.666667 12.8c-38.4 8.533333-72.533333 42.666667-81.066666 81.066666l-12.8 42.666667c-4.266667 17.066667-17.066667 25.6-29.866667 25.6z m-64 512c-12.8 0-25.6-8.533333-29.866667-21.333333l-17.066666-51.2c-4.266667-17.066667-21.333333-34.133333-38.4-38.4l-51.2-17.066667c-12.8-4.266667-21.333333-17.066667-21.333334-29.866667 0-12.8 8.533333-25.6 21.333334-29.866666l51.2-17.066667c17.066667-4.266667 34.133333-21.333333 38.4-38.4l17.066666-51.2c4.266667-12.8 17.066667-21.333333 29.866667-21.333333 12.8 0 25.6 8.533333 29.866667 21.333333l17.066666 51.2c4.266667 17.066667 21.333333 34.133333 38.4 38.4l51.2 17.066667c12.8 4.266667 21.333333 17.066667 21.333334 29.866666 0 12.8-8.533333 25.6-21.333334 29.866667l-51.2 17.066667c-17.066667 4.266667-34.133333 21.333333-38.4 38.4l-17.066666 51.2c-4.266667 12.8-17.066667 21.333333-29.866667 21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-raisehand': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M919.466667 347.733333c0-64-53.333333-117.333333-117.333334-117.333333-12.8 0-23.466667 2.133333-34.133333 4.266667-12.8-51.2-57.6-89.6-115.2-89.6-10.666667 0-21.333333 2.133333-32 4.266666v-14.933333C620.8 70.4 567.466667 17.066667 503.466667 17.066667S386.133333 70.4 386.133333 134.4v14.933333c-10.666667-2.133333-21.333333-4.266667-32-4.266666-64 0-117.333333 53.333333-117.333333 117.333333v174.933333l-4.266667-2.133333c-53.333333-34.133333-110.933333-21.333333-151.466666 4.266667-40.533333 25.6-51.2 83.2-21.333334 121.6l232.533334 300.8c61.866667 87.466667 166.4 142.933333 283.733333 142.933333 91.733333 0 177.066667-25.6 241.066667-83.2s102.4-140.8 102.4-247.466667V347.733333zM836.266667 422.4V674.133333c0 85.333333-32 145.066667-76.8 183.466667-44.8 40.533333-108.8 61.866667-185.6 61.866667-89.6 0-168.533333-42.666667-215.466667-108.8v-2.133334l-230.4-298.666666c23.466667-14.933333 42.666667-14.933333 59.733333-4.266667 2.133333 0 2.133333 2.133333 4.266667 2.133333L260.266667 554.666667c12.8 6.4 29.866667 6.4 42.666666 0 12.8-8.533333 21.333333-21.333333 21.333334-36.266667V264.533333c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v234.666667c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667V134.4c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v362.666667c0 23.466667 19.2 42.666667 42.666667 42.666666s42.666667-19.2 42.666666-42.666666v-234.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v236.8c0 23.466667 19.2 42.666667 42.666667 42.666667s42.666667-19.2 42.666667-42.666667V349.866667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v72.533333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-share': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512.682667 320.426667V85.76c0-5.76 2.304-11.306667 6.4-15.36a22.186667 22.186667 0 0 1 31.146666 0l421.504 418.816a32.298667 32.298667 0 0 1 0 46.08l-421.546666 418.389333a22.101333 22.101333 0 0 1-15.530667 6.357334 21.845333 21.845333 0 0 1-21.973333-21.717334v-233.642666h-43.861334c-170.197333 0-299.093333 34.176-377.386666 134.698666-6.485333 8.362667-13.525333 16.896-23.808 30.165334a11.861333 11.861333 0 0 1-7.68 4.906666c-5.888 0.768-10.496-2.346667-11.946667-9.216A355.029333 355.029333 0 0 1 42.666667 804.096C42.666667 541.269333 253.098667 320.426667 512.682667 320.426667z m0 85.589333c-168.32 0-324.352 124.714667-363.264 270.805333 87.381333-52.224 238.122667-58.026667 362.154666-58.026666H597.333333v149.248l277.632-255.829334L597.333333 234.666667v170.709333l-84.608 0.64z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/document.ts ================================================ import { h } from 'vue' export default { 'app-document': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M13.3333 2.50016H4.16667V17.5002H15.8333V5.01641H13.75C13.6395 5.01641 13.5335 4.97251 13.4554 4.89437C13.3772 4.81623 13.3333 4.71025 13.3333 4.59975V2.50016ZM3.33333 0.833496H14.2379C14.3474 0.833465 14.4558 0.855013 14.557 0.896908C14.6582 0.938804 14.7501 1.00023 14.8275 1.07766L17.2563 3.50725C17.4124 3.66356 17.5001 3.87548 17.5 4.09641V18.3335C17.5 18.5545 17.4122 18.7665 17.2559 18.9228C17.0996 19.079 16.8877 19.1668 16.6667 19.1668H3.33333C3.11232 19.1668 2.90036 19.079 2.74408 18.9228C2.5878 18.7665 2.5 18.5545 2.5 18.3335V1.66683C2.5 1.44582 2.5878 1.23385 2.74408 1.07757C2.90036 0.921293 3.11232 0.833496 3.33333 0.833496ZM6.66667 8.3335H13.3333C13.4438 8.3335 13.5498 8.3774 13.628 8.45554C13.7061 8.53368 13.75 8.63966 13.75 8.75016V9.5835C13.75 9.694 13.7061 9.79998 13.628 9.87812C13.5498 9.95626 13.4438 10.0002 13.3333 10.0002H6.66667C6.55616 10.0002 6.45018 9.95626 6.37204 9.87812C6.2939 9.79998 6.25 9.694 6.25 9.5835V8.75016C6.25 8.63966 6.2939 8.53368 6.37204 8.45554C6.45018 8.3774 6.55616 8.3335 6.66667 8.3335ZM6.66667 12.5002H10.4167C10.4714 12.5002 10.5256 12.5109 10.5761 12.5319C10.6267 12.5528 10.6726 12.5835 10.7113 12.6222C10.75 12.6609 10.7807 12.7068 10.8016 12.7574C10.8226 12.8079 10.8333 12.8621 10.8333 12.9168V13.7502C10.8333 13.8049 10.8226 13.8591 10.8016 13.9096C10.7807 13.9602 10.75 14.0061 10.7113 14.0448C10.6726 14.0835 10.6267 14.1142 10.5761 14.1351C10.5256 14.1561 10.4714 14.1668 10.4167 14.1668H6.66667C6.55616 14.1668 6.45018 14.1229 6.37204 14.0448C6.2939 13.9667 6.25 13.8607 6.25 13.7502V12.9168C6.25 12.8063 6.2939 12.7003 6.37204 12.6222C6.45018 12.5441 6.55616 12.5002 6.66667 12.5002Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-document-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M3.3335 2.08333C3.3335 1.6231 3.70659 1.25 4.16683 1.25H12.3842C12.4959 1.25 12.603 1.29489 12.6813 1.37459L16.5473 5.30784C16.6239 5.38576 16.6668 5.49065 16.6668 5.59992V17.9167C16.6668 18.3769 16.2937 18.75 15.8335 18.75H4.16683C3.70659 18.75 3.3335 18.3769 3.3335 17.9167V2.08333Z', fill: 'currentColor', }), h('path', { d: 'M12.5 1.2666C12.568 1.28633 12.6306 1.32327 12.6812 1.37472L16.5472 5.30797C16.5788 5.34017 16.6047 5.37698 16.6242 5.4168H13.4459C12.9235 5.4168 12.5 4.99328 12.5 4.47085V1.2666Z', fill: '#2B5FD9', }), h('path', { d: 'M6.71305 7.72705C6.48293 7.72705 6.29639 7.9136 6.29639 8.14372V8.82554C6.29639 9.05565 6.48294 9.2422 6.71305 9.2422H13.2871C13.5172 9.2422 13.7038 9.05565 13.7038 8.82554V8.14372C13.7038 7.9136 13.5172 7.72705 13.2871 7.72705H6.71305Z', fill: 'white', }), h('path', { d: 'M6.71305 11.5149C6.48293 11.5149 6.29639 11.7015 6.29639 11.9316V12.6134C6.29639 12.8435 6.48294 13.0301 6.71305 13.0301H9.58342C9.81354 13.0301 10.0001 12.8435 10.0001 12.6134V11.9316C10.0001 11.7015 9.81354 11.5149 9.58342 11.5149H6.71305Z', fill: 'white', }), ], ), ]) }, }, 'app-document-refresh': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 170.666667a85.333333 85.333333 0 0 1 85.333333-85.333334h256a85.333333 85.333333 0 0 1 85.333334 85.333334v256a85.333333 85.333333 0 0 1-85.333334 85.333333h-256a85.333333 85.333333 0 0 1-85.333333-85.333333V170.666667z m85.333333 0v256h256V170.666667h-256zM85.333333 597.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h256a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-256z m85.333334 0v256h256v-256H170.666667zM128 298.666667a213.333333 213.333333 0 0 1 213.333333-213.333334h85.333334v85.333334H341.333333a128 128 0 0 0-128 128h57.514667a12.8 12.8 0 0 1 9.728 21.12l-100.181333 116.906666a12.8 12.8 0 0 1-19.456 0l-100.181334-116.906666A12.8 12.8 0 0 1 70.485333 298.666667H128zM896 725.333333a213.333333 213.333333 0 0 1-213.333333 213.333334h-85.333334v-85.333334h85.333334a128 128 0 0 0 128-128v-21.333333h-57.514667a12.8 12.8 0 0 1-9.728-21.12l100.181333-116.906667a12.8 12.8 0 0 1 19.456 0l100.181334 116.906667a12.8 12.8 0 0 1-9.728 21.12H896v21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-tag': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 85.333333a42.666667 42.666667 0 0 1 30.165333 12.501334l345.045334 345.045333a119.466667 119.466667 0 0 1 0 168.448l-275.413334 275.370667a119.466667 119.466667 0 0 1-169.002666 0.042666l-344.96-344.533333A42.666667 42.666667 0 0 1 85.333333 512V128a42.666667 42.666667 0 0 1 42.666667-42.666667h384z m-17.706667 85.333334H170.666667v323.669333l332.458666 332.074667a34.133333 34.133333 0 0 0 18.773334 9.557333l5.376 0.426667a34.133333 34.133333 0 0 0 24.149333-10.026667l275.242667-275.2a34.133333 34.133333 0 0 0 0.085333-48.042667L494.293333 170.666667zM352 298.666667a53.333333 53.333333 0 1 1 0 106.666666 53.333333 53.333333 0 0 1 0-106.666666z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/folder.ts ================================================ import { h } from 'vue' export default { 'app-folder': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M42.666667 170.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h357.632a42.666667 42.666667 0 0 1 38.144 23.594667L512 213.333333h426.666667a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V170.666667z', fill: '#FFA53D', }), h('path', { d: 'M42.666667 256a42.666667 42.666667 0 0 1 42.666666-42.666667h853.333334a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V256z', fill: '#FFC60A', }), ], ), ]) }, }, 'app-all-menu': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.91683 2.0835H8.3335C8.79373 2.0835 9.16683 2.45659 9.16683 2.91683V8.3335C9.16683 8.79373 8.79373 9.16683 8.3335 9.16683H2.91683C2.45659 9.16683 2.0835 8.79373 2.0835 8.3335V2.91683C2.0835 2.45659 2.45659 2.0835 2.91683 2.0835ZM3.75016 3.75016V7.50016H7.50016V3.75016H3.75016Z', fill: 'currentColor', }), h('path', { d: 'M2.91683 10.8335H8.3335C8.79373 10.8335 9.16683 11.2066 9.16683 11.6668V17.0835C9.16683 17.5437 8.79373 17.9168 8.3335 17.9168H2.91683C2.45659 17.9168 2.0835 17.5437 2.0835 17.0835V11.6668C2.0835 11.2066 2.45659 10.8335 2.91683 10.8335ZM3.75016 16.2502H7.50016V12.5002H3.75016V16.2502Z', fill: 'currentColor', }), h('path', { d: 'M11.6668 2.0835H17.0835C17.5437 2.0835 17.9168 2.45659 17.9168 2.91683V8.3335C17.9168 8.79373 17.5437 9.16683 17.0835 9.16683H11.6668C11.2066 9.16683 10.8335 8.79373 10.8335 8.3335V2.91683C10.8335 2.45659 11.2066 2.0835 11.6668 2.0835ZM12.5002 7.50016H16.2502V3.75016H12.5002V7.50016Z', fill: 'currentColor', }), h('path', { d: 'M11.6668 10.8335H17.0835C17.5437 10.8335 17.9168 11.2066 17.9168 11.6668V17.0835C17.9168 17.5437 17.5437 17.9168 17.0835 17.9168H11.6668C11.2066 17.9168 10.8335 17.5437 10.8335 17.0835V11.6668C10.8335 11.2066 11.2066 10.8335 11.6668 10.8335ZM12.5002 12.5002V16.2502H16.2502V12.5002H12.5002Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-all-menu-active': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M8.33317 1.6665H2.49984C2.0396 1.6665 1.6665 2.0396 1.6665 2.49984V8.33317C1.6665 8.79341 2.0396 9.1665 2.49984 9.1665H8.33317C8.79341 9.1665 9.1665 8.79341 9.1665 8.33317V2.49984C9.1665 2.0396 8.79341 1.6665 8.33317 1.6665Z', fill: 'currentColor', }), h('path', { d: 'M8.33317 10.8332H2.49984C2.0396 10.8332 1.6665 11.2063 1.6665 11.6665V17.4998C1.6665 17.9601 2.0396 18.3332 2.49984 18.3332H8.33317C8.79341 18.3332 9.1665 17.9601 9.1665 17.4998V11.6665C9.1665 11.2063 8.79341 10.8332 8.33317 10.8332Z', fill: 'currentColor', }), h('path', { d: 'M17.4998 1.6665H11.6665C11.2063 1.6665 10.8332 2.0396 10.8332 2.49984V8.33317C10.8332 8.79341 11.2063 9.1665 11.6665 9.1665H17.4998C17.9601 9.1665 18.3332 8.79341 18.3332 8.33317V2.49984C18.3332 2.0396 17.9601 1.6665 17.4998 1.6665Z', fill: 'currentColor', }), h('path', { d: 'M17.4508 10.8332H11.7155C11.2282 10.8332 10.8332 11.2282 10.8332 11.7155V17.4508C10.8332 17.9381 11.2282 18.3332 11.7155 18.3332H17.4508C17.9381 18.3332 18.3332 17.9381 18.3332 17.4508V11.7155C18.3332 11.2282 17.9381 10.8332 17.4508 10.8332Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-add-folder': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M42.666667 170.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h357.632a42.666667 42.666667 0 0 1 38.144 23.594667L512 213.333333h426.666667a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V170.666667zM5.33317 8.33333C5.33317 8.14924 5.48241 8 5.6665 8H7.33317V6.33333C7.33317 6.14924 7.48241 6 7.6665 6H8.33317C8.51726 6 8.6665 6.14924 8.6665 6.33333V8H10.3332C10.5173 8 10.6665 8.14924 10.6665 8.33333V9C10.6665 9.18409 10.5173 9.33333 10.3332 9.33333H8.6665V11C8.6665 11.1841 8.51726 11.3333 8.33317 11.3333H7.6665C7.48241 11.3333 7.33317 11.1841 7.33317 11V9.33333H5.6665C5.48241 9.33333 5.33317 9.18409 5.33317 9V8.33333Z', fill: 'currentColor', }), h('path', { d: 'M0.666504 13.3333V2.66667C0.666504 2.29848 0.964981 2 1.33317 2H6.92115C7.17366 2 7.4045 2.14267 7.51743 2.36852L7.99984 3.33333H14.6348C15.0205 3.33333 15.3332 3.63181 15.3332 4V13.3333C15.3332 13.7015 15.0205 14 14.6348 14H1.36492C0.979194 14 0.666504 13.7015 0.666504 13.3333ZM1.99984 4.66667V12.6667H13.9998V4.66667H1.99984Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-folder-asc': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M3.98719 1.70871C4.40186 1.27104 5.13758 1.56405 5.13758 2.16672V14.3337C5.13748 14.422 5.10235 14.5066 5.03992 14.5691C4.97746 14.6315 4.89287 14.6667 4.80457 14.6667H4.19715C4.15338 14.6667 4.10966 14.6581 4.06922 14.6413C4.02897 14.6246 3.99264 14.5998 3.9618 14.5691C3.93085 14.5381 3.90629 14.5011 3.88953 14.4607C3.87285 14.4204 3.86419 14.3773 3.86414 14.3337V3.83957L2.39246 5.37277C2.33146 5.43553 2.24851 5.47242 2.16102 5.47433C2.07339 5.47614 1.98833 5.4428 1.92469 5.38254L1.43739 4.9216C1.4056 4.89149 1.38003 4.85514 1.36219 4.81516C1.34436 4.77518 1.33505 4.73196 1.33387 4.6882C1.33271 4.64453 1.33975 4.60108 1.35535 4.56027C1.37102 4.51939 1.39457 4.4817 1.42469 4.44992L3.98719 1.70871ZM15.0829 11.9997C15.2209 11.9997 15.3327 12.1118 15.3329 12.2497V13.0837C15.3327 13.2216 15.2208 13.3337 15.0829 13.3337H6.24989C6.11199 13.3336 6.00008 13.2216 5.99989 13.0837V12.2497C6.00004 12.1118 6.11196 11.9998 6.24989 11.9997H15.0829ZM13.0829 7.77805C13.221 7.77805 13.3329 7.88997 13.3329 8.02805V8.86105C13.3329 8.99912 13.221 9.11105 13.0829 9.11105H6.24989C6.11187 9.11099 5.99989 8.99909 5.99989 8.86105V8.02805C5.99989 7.89001 6.11187 7.77811 6.24989 7.77805H13.0829ZM11.0829 3.55539C11.2208 3.55539 11.3327 3.66748 11.3329 3.80539V4.63937C11.3327 4.77731 11.2209 4.88937 11.0829 4.88937H6.24989C6.11197 4.88931 6.00005 4.77727 5.99989 4.63937V3.80539C6.00007 3.66752 6.11198 3.55545 6.24989 3.55539H11.0829Z', fill: 'currentColor', }), h('path', { d: 'M3.98719 1.70871C4.40186 1.27104 5.13758 1.56405 5.13758 2.16672V14.3337C5.13748 14.422 5.10235 14.5066 5.03992 14.5691C4.97746 14.6315 4.89287 14.6667 4.80457 14.6667H4.19715C4.15338 14.6667 4.10966 14.6581 4.06922 14.6413C4.02897 14.6246 3.99264 14.5998 3.9618 14.5691C3.93085 14.5381 3.90629 14.5011 3.88953 14.4607C3.87285 14.4204 3.86419 14.3773 3.86414 14.3337V3.83957L2.39246 5.37277C2.33146 5.43553 2.24851 5.47242 2.16102 5.47433C2.07339 5.47614 1.98833 5.4428 1.92469 5.38254L1.43739 4.9216C1.4056 4.89149 1.38003 4.85514 1.36219 4.81516C1.34436 4.77518 1.33505 4.73196 1.33387 4.6882C1.33271 4.64453 1.33975 4.60108 1.35535 4.56027C1.37102 4.51939 1.39457 4.4817 1.42469 4.44992L3.98719 1.70871ZM15.0829 11.9997C15.2209 11.9997 15.3327 12.1118 15.3329 12.2497V13.0837C15.3327 13.2216 15.2208 13.3337 15.0829 13.3337H6.24989C6.11199 13.3336 6.00008 13.2216 5.99989 13.0837V12.2497C6.00004 12.1118 6.11196 11.9998 6.24989 11.9997H15.0829ZM13.0829 7.77805C13.221 7.77805 13.3329 7.88997 13.3329 8.02805V8.86105C13.3329 8.99912 13.221 9.11105 13.0829 9.11105H6.24989C6.11187 9.11099 5.99989 8.99909 5.99989 8.86105V8.02805C5.99989 7.89001 6.11187 7.77811 6.24989 7.77805H13.0829ZM11.0829 3.55539C11.2208 3.55539 11.3327 3.66748 11.3329 3.80539V4.63937C11.3327 4.77731 11.2209 4.88937 11.0829 4.88937H6.24989C6.11197 4.88931 6.00005 4.77727 5.99989 4.63937V3.80539C6.00007 3.66752 6.11198 3.55545 6.24989 3.55539H11.0829Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-folder-desc': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M5.13997 13.9987C5.13997 14.6014 4.40397 14.8947 3.98931 14.457L1.42664 11.7154C1.39654 11.6836 1.37299 11.6462 1.35735 11.6053C1.34171 11.5644 1.33428 11.5208 1.33549 11.477C1.3367 11.4332 1.34652 11.3901 1.36439 11.3502C1.38226 11.3102 1.40783 11.2741 1.43964 11.244L1.92331 10.7854C1.9551 10.7553 1.99252 10.7317 2.03342 10.7161C2.07432 10.7004 2.1179 10.693 2.16167 10.6942C2.20544 10.6954 2.24854 10.7052 2.28851 10.7231C2.32849 10.741 2.36455 10.7666 2.39464 10.7984L3.80664 12.3254V1.83204C3.80664 1.74363 3.84176 1.65885 3.90427 1.59633C3.96678 1.53382 4.05157 1.4987 4.13997 1.4987H4.80664C4.89505 1.4987 4.97983 1.53382 5.04234 1.59633C5.10485 1.65885 5.13997 1.74363 5.13997 1.83204V13.9987ZM6 2.92793C6 2.78986 6.11193 2.67793 6.25 2.67793H15.0833C15.2214 2.67793 15.3333 2.78986 15.3333 2.92793V3.76127C15.3333 3.89934 15.2214 4.01127 15.0833 4.01127H6.25C6.11193 4.01127 6 3.89934 6 3.76127V2.92793ZM6 7.148C6 7.00993 6.11193 6.898 6.25 6.898H13.0833C13.2214 6.898 13.3333 7.00993 13.3333 7.148V7.98133C13.3333 8.1194 13.2214 8.23133 13.0833 8.23133H6.25C6.11193 8.23133 6 8.1194 6 7.98133V7.148ZM6.25 11.1224C6.11193 11.1224 6 11.2343 6 11.3724V12.2057C6 12.3438 6.11193 12.4557 6.25 12.4557H11.0833C11.2214 12.4557 11.3333 12.3438 11.3333 12.2057V11.3724C11.3333 11.2343 11.2214 11.1224 11.0833 11.1224H6.25Z', fill: 'currentColor', }), h('path', { d: 'M5.13997 13.9987C5.13997 14.6014 4.40397 14.8947 3.98931 14.457L1.42664 11.7154C1.39654 11.6836 1.37299 11.6462 1.35735 11.6053C1.34171 11.5644 1.33428 11.5208 1.33549 11.477C1.3367 11.4332 1.34652 11.3901 1.36439 11.3502C1.38226 11.3102 1.40783 11.2741 1.43964 11.244L1.92331 10.7854C1.9551 10.7553 1.99252 10.7317 2.03342 10.7161C2.07432 10.7004 2.1179 10.693 2.16167 10.6942C2.20544 10.6954 2.24854 10.7052 2.28851 10.7231C2.32849 10.741 2.36455 10.7666 2.39464 10.7984L3.80664 12.3254V1.83204C3.80664 1.74363 3.84176 1.65885 3.90427 1.59633C3.96678 1.53382 4.05157 1.4987 4.13997 1.4987H4.80664C4.89505 1.4987 4.97983 1.53382 5.04234 1.59633C5.10485 1.65885 5.13997 1.74363 5.13997 1.83204V13.9987ZM6 2.92793C6 2.78986 6.11193 2.67793 6.25 2.67793H15.0833C15.2214 2.67793 15.3333 2.78986 15.3333 2.92793V3.76127C15.3333 3.89934 15.2214 4.01127 15.0833 4.01127H6.25C6.11193 4.01127 6 3.89934 6 3.76127V2.92793ZM6 7.148C6 7.00993 6.11193 6.898 6.25 6.898H13.0833C13.2214 6.898 13.3333 7.00993 13.3333 7.148V7.98133C13.3333 8.1194 13.2214 8.23133 13.0833 8.23133H6.25C6.11193 8.23133 6 8.1194 6 7.98133V7.148ZM6.25 11.1224C6.11193 11.1224 6 11.2343 6 11.3724V12.2057C6 12.3438 6.11193 12.4557 6.25 12.4557H11.0833C11.2214 12.4557 11.3333 12.3438 11.3333 12.2057V11.3724C11.3333 11.2343 11.2214 11.1224 11.0833 11.1224H6.25Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-folder-custom': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M10.6667 11.6667C10.7551 11.6667 10.8399 11.7018 10.9024 11.7643C10.9649 11.8268 11 11.9116 11 12V13.3333C11 13.3771 10.9914 13.4204 10.9746 13.4609C10.9579 13.5013 10.9333 13.5381 10.9024 13.569C10.8714 13.6 10.8347 13.6245 10.7942 13.6413C10.7538 13.658 10.7104 13.6667 10.6667 13.6667H9.33333C9.28956 13.6667 9.24621 13.658 9.20577 13.6413C9.16533 13.6245 9.12858 13.6 9.09763 13.569C9.06668 13.5381 9.04213 13.5013 9.02537 13.4609C9.00862 13.4204 9 13.3771 9 13.3333V12C9 11.9562 9.00862 11.9129 9.02537 11.8724C9.04213 11.832 9.06668 11.7952 9.09763 11.7643C9.12858 11.7333 9.16533 11.7088 9.20577 11.692C9.24621 11.6753 9.28956 11.6667 9.33333 11.6667H10.6667ZM6.66667 11.6667C6.75507 11.6667 6.83986 11.7018 6.90237 11.7643C6.96488 11.8268 7 11.9116 7 12V13.3333C7 13.3771 6.99138 13.4204 6.97463 13.4609C6.95787 13.5013 6.93332 13.5381 6.90237 13.569C6.87142 13.6 6.83467 13.6245 6.79423 13.6413C6.75379 13.658 6.71044 13.6667 6.66667 13.6667H5.33333C5.28956 13.6667 5.24621 13.658 5.20577 13.6413C5.16533 13.6245 5.12858 13.6 5.09763 13.569C5.06668 13.5381 5.04213 13.5013 5.02537 13.4609C5.00862 13.4204 5 13.3771 5 13.3333V12C5 11.9116 5.03512 11.8268 5.09763 11.7643C5.16014 11.7018 5.24493 11.6667 5.33333 11.6667H6.66667ZM9.33333 7H10.6667C10.7551 7 10.8399 7.03511 10.9024 7.09763C10.9649 7.16014 11 7.24492 11 7.33333V8.66666C11 8.71044 10.9914 8.75378 10.9746 8.79422C10.9579 8.83467 10.9333 8.87141 10.9024 8.90236C10.8714 8.93332 10.8347 8.95787 10.7942 8.97462C10.7538 8.99137 10.7104 9 10.6667 9H9.33333C9.28956 9 9.24621 8.99137 9.20577 8.97462C9.16533 8.95787 9.12858 8.93332 9.09763 8.90236C9.06668 8.87141 9.04213 8.83467 9.02537 8.79422C9.00862 8.75378 9 8.71044 9 8.66666V7.33333C9 7.28955 9.00862 7.24621 9.02537 7.20577C9.04213 7.16533 9.06668 7.12858 9.09763 7.09763C9.12858 7.06667 9.16533 7.04212 9.20577 7.02537C9.24621 7.00862 9.28956 7 9.33333 7V7ZM5.33333 7H6.66667C6.75507 7 6.83986 7.03511 6.90237 7.09763C6.96488 7.16014 7 7.24492 7 7.33333V8.66666C7 8.71044 6.99138 8.75378 6.97463 8.79422C6.95787 8.83467 6.93332 8.87141 6.90237 8.90236C6.87142 8.93332 6.83467 8.95787 6.79423 8.97462C6.75379 8.99137 6.71044 9 6.66667 9H5.33333C5.28956 9 5.24621 8.99137 5.20577 8.97462C5.16533 8.95787 5.12858 8.93332 5.09763 8.90236C5.06668 8.87141 5.04213 8.83467 5.02537 8.79422C5.00862 8.75378 5 8.71044 5 8.66666V7.33333C5 7.24492 5.03512 7.16014 5.09763 7.09763C5.16014 7.03511 5.24493 7 5.33333 7V7ZM10.6667 2.33333C10.7104 2.33333 10.7538 2.34195 10.7942 2.3587C10.8347 2.37545 10.8714 2.40001 10.9024 2.43096C10.9333 2.46191 10.9579 2.49866 10.9746 2.5391C10.9914 2.57954 11 2.62289 11 2.66666V4C11 4.0884 10.9649 4.17319 10.9024 4.2357C10.8399 4.29821 10.7551 4.33333 10.6667 4.33333H9.33333C9.28956 4.33333 9.24621 4.32471 9.20577 4.30796C9.16533 4.2912 9.12858 4.26665 9.09763 4.2357C9.06668 4.20474 9.04213 4.168 9.02537 4.12756C9.00862 4.08711 9 4.04377 9 4V2.66666C9 2.62289 9.00862 2.57954 9.02537 2.5391C9.04213 2.49866 9.06668 2.46191 9.09763 2.43096C9.12858 2.40001 9.16533 2.37545 9.20577 2.3587C9.24621 2.34195 9.28956 2.33333 9.33333 2.33333H10.6667V2.33333ZM6.66667 2.33333C6.71044 2.33333 6.75379 2.34195 6.79423 2.3587C6.83467 2.37545 6.87142 2.40001 6.90237 2.43096C6.93332 2.46191 6.95787 2.49866 6.97463 2.5391C6.99138 2.57954 7 2.62289 7 2.66666V4C7 4.0884 6.96488 4.17319 6.90237 4.2357C6.83986 4.29821 6.75507 4.33333 6.66667 4.33333H5.33333C5.28956 4.33333 5.24621 4.32471 5.20577 4.30796C5.16533 4.2912 5.12858 4.26665 5.09763 4.2357C5.06668 4.20474 5.04213 4.168 5.02537 4.12756C5.00862 4.08711 5 4.04377 5 4V2.66666C5 2.62289 5.00862 2.57954 5.02537 2.5391C5.04213 2.49866 5.06668 2.46191 5.09763 2.43096C5.12858 2.40001 5.16533 2.37545 5.20577 2.3587C5.24621 2.34195 5.28956 2.33333 5.33333 2.33333H6.66667V2.33333Z', fill: 'currentColor', }), h('path', { d: 'M10.6667 11.6667C10.7551 11.6667 10.8399 11.7018 10.9024 11.7643C10.9649 11.8268 11 11.9116 11 12V13.3333C11 13.3771 10.9914 13.4204 10.9746 13.4609C10.9579 13.5013 10.9333 13.5381 10.9024 13.569C10.8714 13.6 10.8347 13.6245 10.7942 13.6413C10.7538 13.658 10.7104 13.6667 10.6667 13.6667H9.33333C9.28956 13.6667 9.24621 13.658 9.20577 13.6413C9.16533 13.6245 9.12858 13.6 9.09763 13.569C9.06668 13.5381 9.04213 13.5013 9.02537 13.4609C9.00862 13.4204 9 13.3771 9 13.3333V12C9 11.9562 9.00862 11.9129 9.02537 11.8724C9.04213 11.832 9.06668 11.7952 9.09763 11.7643C9.12858 11.7333 9.16533 11.7088 9.20577 11.692C9.24621 11.6753 9.28956 11.6667 9.33333 11.6667H10.6667ZM6.66667 11.6667C6.75507 11.6667 6.83986 11.7018 6.90237 11.7643C6.96488 11.8268 7 11.9116 7 12V13.3333C7 13.3771 6.99138 13.4204 6.97463 13.4609C6.95787 13.5013 6.93332 13.5381 6.90237 13.569C6.87142 13.6 6.83467 13.6245 6.79423 13.6413C6.75379 13.658 6.71044 13.6667 6.66667 13.6667H5.33333C5.28956 13.6667 5.24621 13.658 5.20577 13.6413C5.16533 13.6245 5.12858 13.6 5.09763 13.569C5.06668 13.5381 5.04213 13.5013 5.02537 13.4609C5.00862 13.4204 5 13.3771 5 13.3333V12C5 11.9116 5.03512 11.8268 5.09763 11.7643C5.16014 11.7018 5.24493 11.6667 5.33333 11.6667H6.66667ZM9.33333 7H10.6667C10.7551 7 10.8399 7.03511 10.9024 7.09763C10.9649 7.16014 11 7.24492 11 7.33333V8.66666C11 8.71044 10.9914 8.75378 10.9746 8.79422C10.9579 8.83467 10.9333 8.87141 10.9024 8.90236C10.8714 8.93332 10.8347 8.95787 10.7942 8.97462C10.7538 8.99137 10.7104 9 10.6667 9H9.33333C9.28956 9 9.24621 8.99137 9.20577 8.97462C9.16533 8.95787 9.12858 8.93332 9.09763 8.90236C9.06668 8.87141 9.04213 8.83467 9.02537 8.79422C9.00862 8.75378 9 8.71044 9 8.66666V7.33333C9 7.28955 9.00862 7.24621 9.02537 7.20577C9.04213 7.16533 9.06668 7.12858 9.09763 7.09763C9.12858 7.06667 9.16533 7.04212 9.20577 7.02537C9.24621 7.00862 9.28956 7 9.33333 7V7ZM5.33333 7H6.66667C6.75507 7 6.83986 7.03511 6.90237 7.09763C6.96488 7.16014 7 7.24492 7 7.33333V8.66666C7 8.71044 6.99138 8.75378 6.97463 8.79422C6.95787 8.83467 6.93332 8.87141 6.90237 8.90236C6.87142 8.93332 6.83467 8.95787 6.79423 8.97462C6.75379 8.99137 6.71044 9 6.66667 9H5.33333C5.28956 9 5.24621 8.99137 5.20577 8.97462C5.16533 8.95787 5.12858 8.93332 5.09763 8.90236C5.06668 8.87141 5.04213 8.83467 5.02537 8.79422C5.00862 8.75378 5 8.71044 5 8.66666V7.33333C5 7.24492 5.03512 7.16014 5.09763 7.09763C5.16014 7.03511 5.24493 7 5.33333 7V7ZM10.6667 2.33333C10.7104 2.33333 10.7538 2.34195 10.7942 2.3587C10.8347 2.37545 10.8714 2.40001 10.9024 2.43096C10.9333 2.46191 10.9579 2.49866 10.9746 2.5391C10.9914 2.57954 11 2.62289 11 2.66666V4C11 4.0884 10.9649 4.17319 10.9024 4.2357C10.8399 4.29821 10.7551 4.33333 10.6667 4.33333H9.33333C9.28956 4.33333 9.24621 4.32471 9.20577 4.30796C9.16533 4.2912 9.12858 4.26665 9.09763 4.2357C9.06668 4.20474 9.04213 4.168 9.02537 4.12756C9.00862 4.08711 9 4.04377 9 4V2.66666C9 2.62289 9.00862 2.57954 9.02537 2.5391C9.04213 2.49866 9.06668 2.46191 9.09763 2.43096C9.12858 2.40001 9.16533 2.37545 9.20577 2.3587C9.24621 2.34195 9.28956 2.33333 9.33333 2.33333H10.6667V2.33333ZM6.66667 2.33333C6.71044 2.33333 6.75379 2.34195 6.79423 2.3587C6.83467 2.37545 6.87142 2.40001 6.90237 2.43096C6.93332 2.46191 6.95787 2.49866 6.97463 2.5391C6.99138 2.57954 7 2.62289 7 2.66666V4C7 4.0884 6.96488 4.17319 6.90237 4.2357C6.83986 4.29821 6.75507 4.33333 6.66667 4.33333H5.33333C5.28956 4.33333 5.24621 4.32471 5.20577 4.30796C5.16533 4.2912 5.12858 4.26665 5.09763 4.2357C5.06668 4.20474 5.04213 4.168 5.02537 4.12756C5.00862 4.08711 5 4.04377 5 4V2.66666C5 2.62289 5.00862 2.57954 5.02537 2.5391C5.04213 2.49866 5.06668 2.46191 5.09763 2.43096C5.12858 2.40001 5.16533 2.37545 5.20577 2.3587C5.24621 2.34195 5.28956 2.33333 5.33333 2.33333H6.66667V2.33333Z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/knowledge.ts ================================================ import { h } from 'vue' export default { 'app-vectorization': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 170.666667a85.333333 85.333333 0 0 1 85.333333-85.333334h256a85.333333 85.333333 0 0 1 85.333334 85.333334v256a85.333333 85.333333 0 0 1-85.333334 85.333333h-256a85.333333 85.333333 0 0 1-85.333333-85.333333V170.666667z m85.333333 0v256h256V170.666667h-256zM85.333333 597.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h256a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-256z m85.333334 0v256h256v-256H170.666667zM128 298.666667a213.333333 213.333333 0 0 1 213.333333-213.333334h85.333334v85.333334H341.333333a128 128 0 0 0-128 128h57.514667a12.8 12.8 0 0 1 9.728 21.12l-100.181333 116.906666a12.8 12.8 0 0 1-19.456 0l-100.181334-116.906666A12.8 12.8 0 0 1 70.485333 298.666667H128zM896 725.333333a213.333333 213.333333 0 0 1-213.333333 213.333334h-85.333334v-85.333334h85.333334a128 128 0 0 0 128-128v-21.333333h-57.514667a12.8 12.8 0 0 1-9.728-21.12l100.181333-116.906667a12.8 12.8 0 0 1 19.456 0l100.181334 116.906667a12.8 12.8 0 0 1-9.728 21.12H896v21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-problems': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-hit-test': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M1.6665 9.99986C1.6665 5.3975 5.39748 1.66653 9.99984 1.66653H10.8332V3.3332H9.99984C6.31795 3.3332 3.33317 6.31797 3.33317 9.99986C3.33317 13.6818 6.31795 16.6665 9.99984 16.6665C13.6817 16.6665 16.6665 13.6818 16.6665 9.99986V9.16653H18.3332V9.99986C18.3332 14.6022 14.6022 18.3332 9.99984 18.3332C5.39748 18.3332 1.6665 14.6022 1.6665 9.99986Z', fill: 'currentColor', fillRule: 'evenodd', clipRule: 'evenodd', }), h('path', { d: 'M5.4165 9.99986C5.4165 7.46854 7.46852 5.41653 9.99984 5.41653H10.8332V7.0832H9.99984C8.38899 7.0832 7.08317 8.38902 7.08317 9.99986C7.08317 11.6107 8.38899 12.9165 9.99984 12.9165C11.6107 12.9165 12.9165 11.6107 12.9165 9.99986V9.16653H14.5832V9.99986C14.5832 12.5312 12.5312 14.5832 9.99984 14.5832C7.46852 14.5832 5.4165 12.5312 5.4165 9.99986Z', fill: 'currentColor', fillRule: 'evenodd', clipRule: 'evenodd', }), h('path', { d: 'M13.2138 6.78296C13.5394 7.10825 13.5397 7.63588 13.2144 7.96147L10.5894 10.5889C10.2641 10.9145 9.73644 10.9147 9.41085 10.5894C9.08527 10.2641 9.08502 9.73651 9.41031 9.41092L12.0353 6.7835C12.3606 6.45792 12.8882 6.45767 13.2138 6.78296Z', fill: 'currentColor', fillRule: 'evenodd', clipRule: 'evenodd', }), h('path', { d: 'M15.1942 1.72962C15.506 1.8584 15.7095 2.16249 15.7095 2.49986V4.29161H17.4998C17.8365 4.29161 18.1401 4.49423 18.2693 4.80516C18.3985 5.11608 18.3279 5.47421 18.0904 5.71284L15.8508 7.96276C15.6944 8.11987 15.4819 8.2082 15.2602 8.2082H12.6248C12.1645 8.2082 11.7914 7.8351 11.7914 7.37486V4.76086C11.7914 4.54046 11.8787 4.32904 12.0342 4.17287L14.2856 1.91186C14.5237 1.6728 14.8824 1.60085 15.1942 1.72962ZM13.4581 5.105V6.54153H14.9139L15.4945 5.95828H14.8761C14.4159 5.95828 14.0428 5.58518 14.0428 5.12495V4.51779L13.4581 5.105Z', fill: 'currentColor', fillRule: 'evenodd', clipRule: 'evenodd', }), ], ), ]) }, }, 'app-quxiaoguanlian': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M544 298.688a32 32 0 0 1 32-32h320c41.216 0 74.688 33.408 74.688 74.624V640c0 41.216-33.472 74.688-74.688 74.688h-85.312a32 32 0 1 1 0-64H896a10.688 10.688 0 0 0 10.688-10.688V341.312A10.688 10.688 0 0 0 896 330.688H576a32 32 0 0 1-32-32zM53.312 341.312c0-41.216 33.472-74.624 74.688-74.624h106.688a32 32 0 1 1 0 64H128a10.688 10.688 0 0 0-10.688 10.624V640c0 5.888 4.8 10.688 10.688 10.688h320a32 32 0 1 1 0 64H128A74.688 74.688 0 0 1 53.312 640V341.312zM282.432 100.416a32 32 0 0 1 43.84 11.392l426.624 725.312a32 32 0 0 1-55.168 32.448L271.104 144.256a32 32 0 0 1 11.328-43.84zM650.688 490.688a32 32 0 0 1 32-32H768a32 32 0 1 1 0 64h-85.312a32 32 0 0 1-32-32zM224 490.688a32 32 0 0 1 32-32h85.312a32 32 0 1 1 0 64H256a32 32 0 0 1-32-32z', fill: 'currentColor', }), ], ), ]) }, }, 'app-drag-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M682.666667 746.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.248 21.248 0 0 1-21.333333 21.333334h-85.333334a21.248 21.248 0 0 1-21.333333-21.333334v-85.333333a21.248 21.248 0 0 1 21.333333-21.333333h85.333334z m-256 0a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.248 21.248 0 0 1-21.333333 21.333334H341.333333a21.290667 21.290667 0 0 1-21.333333-21.333334v-85.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334z m170.666666-298.666667h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333h-85.333334a21.248 21.248 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z m-256 0h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333H341.333333a21.290667 21.290667 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333z m341.333334-298.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333V170.666667a21.290667 21.290667 0 0 1 21.333333-21.333334h85.333334z m-256 0a21.333333 21.333333 0 0 1 21.333333 21.333334v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333333H341.333333a21.333333 21.333333 0 0 1-21.333333-21.333333V170.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h85.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-workflow': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M163.029333 207.189333C204.586667 179.114667 252.842667 170.666667 285.056 170.666667H640a42.666667 42.666667 0 1 0 0 85.333333H285.098667c-20.096 0-50.432 5.76-74.325334 21.930667-21.546667 14.506667-40.106667 38.656-40.106666 83.754666 0 45.141333 18.645333 69.845333 40.448 84.821334 24.021333 16.512 54.272 22.528 73.984 22.528h457.173333c32.554667 0 80.170667 8.832 120.96 37.376 43.093333 30.122667 75.434667 80.341333 75.434667 154.453333 0 74.154667-32.341333 124.458667-75.306667 154.752-40.746667 28.672-88.405333 37.717333-121.088 37.717333H384a42.666667 42.666667 0 1 0 0-85.333333h358.272c19.669333 0 48.896-5.973333 71.936-22.186667 20.778667-14.634667 39.125333-39.168 39.125333-84.906666s-18.346667-70.101333-38.997333-84.608c-22.997333-16.042667-52.224-21.930667-72.064-21.930667H285.098667c-32.682667 0-80.938667-9.045333-122.368-37.546667C119.04 486.698667 85.333333 436.309333 85.333333 361.642667c0-74.794667 33.792-124.842667 77.696-154.453334z', fill: 'currentColor', }), h('path', { d: 'M384 768a42.666667 42.666667 0 1 0 0 85.333333H128a42.666667 42.666667 0 1 1 0-85.333333h256zM640 256a42.666667 42.666667 0 1 0 0-85.333333h253.653333a42.666667 42.666667 0 1 1 0 85.333333H640z', fill: 'currentColor', }), h('path', { d: 'M640 170.666667a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m-128 42.666666a128 128 0 1 1 256 0 128 128 0 0 1-256 0zM384 768a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m-128 42.666667a128 128 0 1 1 256 0 128 128 0 0 1-256 0z', fill: 'currentColor', }), ], ), ]) }, }, 'app-execution-record': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M682.666667 42.666667H341.333333c-26.197333 0-42.666667 16.512-42.666666 42.666666v42.666667H170.666667c-29.269333 0-42.666667 16.512-42.666667 42.666667v768c0 26.197333 13.397333 42.666667 42.666667 42.666666h682.666666c29.269333 0 42.666667-16.512 42.666667-42.666666V170.666667c0-26.197333-13.397333-42.666667-42.666667-42.666667h-128v85.333333h85.333334v682.666667H213.333333V213.333333h85.333334v42.666667c0 26.154667 16.469333 42.666667 42.666666 42.666667h341.333334c26.154667 0 42.666667-16.512 42.666666-42.666667V85.333333c0-26.197333-16.512-42.666667-42.666666-42.666666zM384 213.333333V128h256v85.333333H384z', fill: 'currentColor', }), h('path', { d: 'M321.024 469.333333h381.952c12.373333 0 22.357333 9.557333 22.357333 21.333334v42.666666c0 11.776-10.026667 21.333333-22.357333 21.333334H321.024A21.845333 21.845333 0 0 1 298.666667 533.333333v-42.666666c0-11.776 10.026667-21.333333 22.357333-21.333334zM702.976 640H321.024a21.845333 21.845333 0 0 0-22.357333 21.333333v42.666667c0 11.776 10.026667 21.333333 22.357333 21.333333h381.952c12.373333 0 22.357333-9.557333 22.357333-21.333333v-42.666667c0-11.776-10.026667-21.333333-22.357333-21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-to-import-doc': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M682.666667 128H213.333333v768h597.333334V256.853333h-106.666667a21.333333 21.333333 0 0 1-21.333333-21.333333V128zM170.666667 42.666667h558.293333a42.666667 42.666667 0 0 1 30.208 12.501333l124.373333 124.373333a42.666667 42.666667 0 0 1 12.458667 30.165334V938.666667a42.666667 42.666667 0 0 1-42.666667 42.666666H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666z', fill: 'currentColor', }), h('path', { d: 'M469.333333 362.666667a21.333333 21.333333 0 0 1 21.333334-21.333334h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333334V469.333333h106.666666a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334H554.666667v106.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333334V554.666667H362.666667a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334H469.333333V362.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-template-center': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M213.333333 128h469.333334v107.52a21.333333 21.333333 0 0 0 21.333333 21.333333H810.666667V896H213.333333V128z m515.626667-85.333333H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v853.333334a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666666a42.666667 42.666667 0 0 0 42.666667-42.666666V209.749333a42.666667 42.666667 0 0 0-12.501333-30.208l-124.330667-124.373333A42.666667 42.666667 0 0 0 729.002667 42.666667zM320 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384z m149.333333 192a21.333333 21.333333 0 0 1 21.333334-21.333333h213.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v213.333334a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-213.333334zM320 512a21.333333 21.333333 0 0 0-21.333333 21.333333v213.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/menu.ts ================================================ import { h } from 'vue' export default { 'app-resource-authorization': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M10.354 0.484228C10.1252 0.417397 9.88209 0.417366 9.6533 0.484138L2.56643 2.55237C2.0332 2.70799 1.66663 3.19683 1.66663 3.75232V7.92864C1.66663 12.9588 4.8543 17.43 9.59603 19.076C9.85818 19.167 10.144 19.167 10.4061 19.076C15.1466 17.4299 18.3333 12.9597 18.3333 7.93073V3.75223C18.3333 3.19687 17.9669 2.7081 17.4338 2.55238L10.354 0.484228ZM3.33329 4.06476L10.0034 2.11815L16.6666 4.0646V7.93073C16.6666 12.199 13.9934 15.9986 10.001 17.4512C6.00742 15.9986 3.33329 12.1981 3.33329 7.92864V4.06476Z', fill: 'currentColor', }), h('path', { d: 'M10 10C8.61917 10 7.5 8.87917 7.5 7.5C7.5 6.12083 8.61917 5 10 5C11.3808 5 12.5 6.12083 12.5 7.5C12.5 8.87917 11.3808 10 10 10ZM10 8.33333C10.4604 8.33333 10.8333 7.95833 10.8333 7.5C10.8333 7.04167 10.4604 6.66667 10 6.66667C9.53958 6.66667 9.16667 7.04167 9.16667 7.5C9.16667 7.95833 9.53958 8.33333 10 8.33333Z', fill: 'currentColor', }), h('path', { d: 'M10.8333 14.5918C10.8333 14.8173 10.6467 15 10.4166 15H9.58329C9.35317 15 9.16663 14.8173 9.16663 14.5918L9.16663 8.7415C9.16663 8.51607 9.35317 8.33333 9.58329 8.33333H10.4166C10.6467 8.33333 10.8333 8.51607 10.8333 8.7415V14.5918Z', fill: 'currentColor', }), h('path', { d: 'M10.3571 12.5C10.1599 12.5 10 12.3135 10 12.0834V11.25C10 11.0199 10.1599 10.8334 10.3571 10.8334H12.1429C12.3401 10.8334 12.5 11.0199 12.5 11.25V12.0834C12.5 12.3135 12.3401 12.5 12.1429 12.5H10.3571Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-resource-authorization-active': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M9.65332 0.483805C9.88209 0.417057 10.1257 0.416982 10.3545 0.483805L17.4336 2.55216C17.9667 2.70789 18.333 3.197 18.333 3.75236V7.93107C18.3329 12.9599 15.1465 17.4295 10.4062 19.0756C10.1441 19.1666 9.85786 19.1666 9.5957 19.0756C4.85437 17.4295 1.66718 12.959 1.66699 7.92912V3.75236C1.66699 3.19688 2.03317 2.70779 2.56641 2.55216L9.65332 0.483805ZM10 5.00041C8.61917 5.00041 7.5 6.12124 7.5 7.50041C7.50016 8.58756 8.19605 9.51433 9.16699 9.85783V14.5922C9.16717 14.8174 9.35315 15.0002 9.58301 15.0004H10.417C10.6469 15.0002 10.8328 14.8174 10.833 14.5922V12.5004H12.1426C12.3398 12.5004 12.5 12.3135 12.5 12.0834V11.2504C12.5 11.0203 12.3398 10.8334 12.1426 10.8334H10.833V9.85783C11.8039 9.51433 12.4998 8.58756 12.5 7.50041C12.5 6.12124 11.3808 5.00041 10 5.00041ZM10 6.66642C10.4604 6.66642 10.833 7.04207 10.833 7.50041C10.8328 7.95825 10.4608 8.33289 10.001 8.33341H9.99902C9.53918 8.33288 9.16719 7.95825 9.16699 7.50041C9.16699 7.04207 9.53958 6.66642 10 6.66642Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-shared': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M10.4015 9.13532C10.3663 9.01295 10.3474 8.88365 10.3474 8.74993C10.3474 7.98287 10.9692 7.36104 11.7363 7.36104C12.5033 7.36104 13.1251 7.98287 13.1251 8.74993C13.1251 9.51699 12.5033 10.1388 11.7363 10.1388C11.3532 10.1388 11.0064 9.98377 10.7551 9.733L9.25154 10.6215C9.2868 10.7439 9.3057 10.8732 9.3057 11.0069C9.3057 11.0989 9.29675 11.1889 9.27967 11.2759L11.195 12.1952C11.4497 11.8932 11.831 11.7013 12.2571 11.7013C13.0242 11.7013 13.646 12.3231 13.646 13.0902C13.646 13.8573 13.0242 14.4791 12.2571 14.4791C11.49 14.4791 10.8682 13.8573 10.8682 13.0902C10.8682 12.9982 10.8772 12.9082 10.8942 12.8212L8.97894 11.9019C8.72417 12.2039 8.3429 12.3958 7.91681 12.3958C7.14975 12.3958 6.52792 11.7739 6.52792 11.0069C6.52792 10.2398 7.14975 9.61799 7.91681 9.61799C8.29985 9.61799 8.64667 9.77304 8.89793 10.0238L10.4015 9.13532Z', fill: 'currentColor', }), h('path', { d: 'M0.833344 3.33333V16.6667C0.833344 17.1269 1.22421 17.5 1.70636 17.5H18.2937C18.7758 17.5 19.1667 17.1269 19.1667 16.6667V5C19.1667 4.53976 18.7758 4.16667 18.2937 4.16667H10L9.397 2.96066C9.25584 2.67834 8.96729 2.5 8.65165 2.5H1.66668C1.20644 2.5 0.833344 2.8731 0.833344 3.33333ZM2.50001 15.8333V5.83333H17.5V15.8333H2.50001Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-shared-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M0.833334 3.33333C0.833334 2.8731 1.20643 2.5 1.66667 2.5H8.65164C8.96728 2.5 9.25583 2.67834 9.39699 2.96066L10 4.16667H18.3333C18.7936 4.16667 19.1667 4.53976 19.1667 5V16.6667C19.1667 17.1269 18.7936 17.5 18.3333 17.5H1.66667C1.20643 17.5 0.833334 17.1269 0.833334 16.6667V3.33333Z', fill: 'currentColor', }), h('path', { d: 'M10.5403 9.27428C10.505 9.15191 10.4861 9.02261 10.4861 8.88889C10.4861 8.12183 11.1079 7.5 11.875 7.5C12.6421 7.5 13.2639 8.12183 13.2639 8.88889C13.2639 9.65595 12.6421 10.2778 11.875 10.2778C11.492 10.2778 11.1451 10.1227 10.8939 9.87195L9.39028 10.7604C9.42555 10.8828 9.44444 11.0121 9.44444 11.1458C9.44444 11.2379 9.43549 11.3278 9.41841 11.4149L11.3337 12.3342C11.5885 12.0321 11.9697 11.8403 12.3958 11.8403C13.1629 11.8403 13.7847 12.4621 13.7847 13.2292C13.7847 13.9962 13.1629 14.6181 12.3958 14.6181C11.6288 14.6181 11.0069 13.9962 11.0069 13.2292C11.0069 13.1371 11.0159 13.0472 11.033 12.9601L9.11769 12.0408C8.86291 12.3429 8.48164 12.5347 8.05556 12.5347C7.28849 12.5347 6.66667 11.9129 6.66667 11.1458C6.66667 10.3788 7.28849 9.75694 8.05556 9.75694C8.43859 9.75694 8.78541 9.912 9.03667 10.1628L10.5403 9.27428Z', fill: 'white', }), ], ), ]) }, }, 'app-setting': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M184.704 841.941333l-13.269333-14.421333a465.536 465.536 0 0 1-101.802667-176.938667l-5.76-18.602666L151.253333 512 63.872 392.021333l5.76-18.602666a465.493333 465.493333 0 0 1 101.802667-176.938667l13.226666-14.464 146.901334 16.042667 59.648-135.936 19.114666-4.309334A462.634667 462.634667 0 0 1 512 46.506667c34.56 0 68.565333 3.797333 101.717333 11.264l19.114667 4.266666 59.648 135.978667 146.858667-16.042667 13.269333 14.506667a465.493333 465.493333 0 0 1 101.802667 176.896l5.76 18.602667L872.789333 512l87.381334 119.978667-5.76 18.602666a465.493333 465.493333 0 0 1-101.802667 176.938667l-13.226667 14.421333-146.901333-16.042666-59.648 135.978666-19.114667 4.309334a462.549333 462.549333 0 0 1-203.392 0l-19.114666-4.266667-59.648-136.021333-146.858667 16.085333z m148.693333-94.293333a63.488 63.488 0 0 1 65.024 37.632l47.786667 108.970667a386.133333 386.133333 0 0 0 131.584 0l47.786667-108.970667a63.488 63.488 0 0 1 65.066666-37.589333l117.504 12.8c28.373333-34.133333 50.773333-72.96 66.048-114.773334l-70.186666-96.341333a63.488 63.488 0 0 1 0-74.752l70.186666-96.341333a387.925333 387.925333 0 0 0-66.048-114.773334l-117.504 12.8a63.488 63.488 0 0 1-65.024-37.589333l-47.786666-109.013333a386.261333 386.261333 0 0 0-131.584 0l-47.786667 109.013333a63.488 63.488 0 0 1-65.066667 37.589333l-117.504-12.8c-28.416 34.133333-50.773333 72.96-66.048 114.773334l70.144 96.341333c16.213333 22.272 16.213333 52.48 0 74.752l-70.144 96.341333c15.274667 41.813333 37.632 80.64 66.048 114.773334l117.504-12.8zM512 705.962667c-106.752 0-193.237333-86.869333-193.237333-193.92 0-107.093333 86.485333-193.962667 193.237333-193.962667 106.709333 0 193.194667 86.869333 193.194667 193.962667 0 107.093333-86.485333 193.92-193.194667 193.92z m0-77.568c63.786667 0 115.626667-52.053333 115.626667-116.352A116.010667 116.010667 0 0 0 512 395.648 116.010667 116.010667 0 0 0 396.373333 512 116.010667 116.010667 0 0 0 512 628.352z', fill: 'currentColor', }), ], ), ]) }, }, 'app-setting-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M167.125333 830.208A468.864 468.864 0 0 1 64 651.776l74.666667-101.973333a64 64 0 0 0 0-75.605334L64 372.224a468.906667 468.906667 0 0 1 103.125333-178.432l125.44 13.653333a64 64 0 0 0 65.493334-37.802666l50.944-115.626667A470.613333 470.613333 0 0 1 512 42.666667c35.413333 0 69.845333 3.925333 102.997333 11.349333l50.944 115.626667a64 64 0 0 0 65.493334 37.802666l125.44-13.653333A468.821333 468.821333 0 0 1 960 372.224l-74.666667 101.973333a64 64 0 0 0 0 75.605334l74.666667 101.973333a468.778667 468.778667 0 0 1-103.125333 178.432l-125.44-13.653333a64 64 0 0 0-65.493334 37.802666l-50.944 115.626667c-33.152 7.424-67.626667 11.349333-102.997333 11.349333-35.413333 0-69.845333-3.925333-102.997333-11.349333l-50.944-115.626667a64 64 0 0 0-65.493334-37.802666l-125.44 13.653333zM512 682.666667a170.666667 170.666667 0 1 0 0-341.333334 170.666667 170.666667 0 0 0 0 341.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-role': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M12.5 4.16667C11.35 4.16667 10.4167 5.09958 10.4167 6.25C10.4167 7.40042 11.35 8.33333 12.5 8.33333C13.65 8.33333 14.5833 7.40042 14.5833 6.25C14.5833 5.09958 13.65 4.16667 12.5 4.16667ZM8.75 6.25C8.75 4.17875 10.4292 2.5 12.5 2.5C14.5708 2.5 16.25 4.17875 16.25 6.25C16.25 8.32125 14.5708 10 12.5 10C10.4292 10 8.75 8.32125 8.75 6.25ZM10.2792 12.5C8.7625 12.5 7.5 13.7488 7.5 15.3333V16.6667H17.5V15.3333C17.5 13.7488 16.2375 12.5 14.7208 12.5H10.2792ZM5.83333 15.3333C5.83333 12.8479 7.825 10.8333 10.2792 10.8333H14.7208C17.175 10.8333 19.1667 12.8479 19.1667 15.3333V17.5833C19.1667 17.9975 18.8333 18.3333 18.425 18.3333H6.575C6.16667 18.3333 5.83333 17.9975 5.83333 17.5833V15.3333Z', fill: 'currentColor', }), h('path', { d: 'M7.08333 4.99998H2.5V16.6666H3.75C3.98012 16.6666 4.16667 16.8532 4.16667 17.0833V17.9166C4.16667 18.1468 3.98012 18.3333 3.75 18.3333H1.94036C1.25 18.3333 0.833334 17.9166 0.833334 17.0833V4.44034C0.833334 3.74998 1.25 3.33331 1.94036 3.33331H7.08333C7.31345 3.33331 7.5 3.51986 7.5 3.74998V4.58331C7.5 4.81343 7.31345 4.99998 7.08333 4.99998Z', fill: 'currentColor', }), h('path', { d: 'M3.66667 7.49998H7.16667C7.25507 7.49998 7.33986 7.54388 7.40237 7.62202C7.46488 7.70016 7.5 7.80614 7.5 7.91665V8.74998C7.5 8.86049 7.46488 8.96647 7.40237 9.04461C7.33986 9.12275 7.25507 9.16665 7.16667 9.16665H3.66667C3.57826 9.16665 3.49348 9.12275 3.43097 9.04461C3.36845 8.96647 3.33333 8.86049 3.33333 8.74998V7.91665C3.33333 7.80614 3.36845 7.70016 3.43097 7.62202C3.49348 7.54388 3.57826 7.49998 3.66667 7.49998Z', fill: 'currentColor', }), h('path', { d: 'M3.58333 9.99998H5.58333C5.64964 9.99998 5.71323 10.0439 5.76011 10.122C5.80699 10.2002 5.83333 10.3061 5.83333 10.4166V11.25C5.83333 11.3605 5.80699 11.4665 5.76011 11.5446C5.71323 11.6227 5.64964 11.6666 5.58333 11.6666H3.58333C3.51703 11.6666 3.45344 11.6227 3.40656 11.5446C3.35967 11.4665 3.33333 11.3605 3.33333 11.25V10.4166C3.33333 10.3061 3.35967 10.2002 3.40656 10.122C3.45344 10.0439 3.51703 9.99998 3.58333 9.99998Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-role-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M12.5 2.5C10.4292 2.5 8.75 4.17875 8.75 6.25C8.75 8.32125 10.4292 10 12.5 10C14.5708 10 16.25 8.32125 16.25 6.25C16.25 4.17875 14.5708 2.5 12.5 2.5Z', fill: 'currentColor', }), h('path', { d: 'M10.2792 10.8333C7.825 10.8333 5.83333 12.8479 5.83333 15.3333V17.5833C5.83333 17.9975 6.16667 18.3333 6.575 18.3333H18.425C18.8333 18.3333 19.1667 17.9975 19.1667 17.5833V15.3333C19.1667 12.8479 17.175 10.8333 14.7208 10.8333H10.2792Z', fill: 'currentColor', }), h('path', { d: 'M7.08333 4.99998H2.5V16.6666H3.75C3.98012 16.6666 4.16667 16.8532 4.16667 17.0833V17.9166C4.16667 18.1468 3.98012 18.3333 3.75 18.3333H1.94036C1.25 18.3333 0.833334 17.9166 0.833334 17.0833V4.44034C0.833334 3.74998 1.25 3.33331 1.94036 3.33331H7.08333C7.31345 3.33331 7.5 3.51986 7.5 3.74998V4.58331C7.5 4.81343 7.31345 4.99998 7.08333 4.99998Z', fill: 'currentColor', }), h('path', { d: 'M3.66667 7.49998H7.16667C7.25507 7.49998 7.33986 7.54388 7.40237 7.62202C7.46488 7.70016 7.5 7.80614 7.5 7.91665V8.74998C7.5 8.86049 7.46488 8.96647 7.40237 9.04461C7.33986 9.12275 7.25507 9.16665 7.16667 9.16665H3.66667C3.57826 9.16665 3.49348 9.12275 3.43097 9.04461C3.36845 8.96647 3.33333 8.86049 3.33333 8.74998V7.91665C3.33333 7.80614 3.36845 7.70016 3.43097 7.62202C3.49348 7.54388 3.57826 7.49998 3.66667 7.49998Z', fill: 'currentColor', }), h('path', { d: 'M3.58333 9.99998H5.58333C5.64964 9.99998 5.71323 10.0439 5.76011 10.122C5.80699 10.2002 5.83333 10.3061 5.83333 10.4166V11.25C5.83333 11.3605 5.80699 11.4665 5.76011 11.5446C5.71323 11.6227 5.64964 11.6666 5.58333 11.6666H3.58333C3.51703 11.6666 3.45344 11.6227 3.40656 11.5446C3.35967 11.4665 3.33333 11.3605 3.33333 11.25V10.4166C3.33333 10.3061 3.35967 10.2002 3.40656 10.122C3.45344 10.0439 3.51703 9.99998 3.58333 9.99998Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-workspace': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M523.477333 113.92l429.568 273.408a21.333333 21.333333 0 0 1 0 36.010667L523.52 696.704a21.333333 21.333333 0 0 1-22.912 0L70.954667 423.338667a21.333333 21.333333 0 0 1 0-36.010667l429.610666-273.365333a21.333333 21.333333 0 0 1 22.912 0zM201.6 405.333333L512 602.88l310.4-197.546667L512 207.786667 201.6 405.333333z', fill: 'currentColor', }), h('path', { d: 'M110.805333 592.469333a21.333333 21.333333 0 0 0-29.354666 7.04l-22.314667 36.394667a21.333333 21.333333 0 0 0 7.04 29.312l390.613333 239.530667a84.992 84.992 0 0 0 89.088 0l390.613334-239.530667a21.333333 21.333333 0 0 0 7.04-29.312l-22.314667-36.394667a21.333333 21.333333 0 0 0-29.312-7.04L506.88 828.586667a10.666667 10.666667 0 0 1-11.136 0l-384.981333-236.074667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-workspace-active': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M10.2237 2.22566L18.6143 7.56512C18.8716 7.72885 18.8716 8.10444 18.6143 8.26817L10.2237 13.6076C10.0872 13.6945 9.91279 13.6945 9.7763 13.6076L1.38573 8.26817C1.12844 8.10444 1.12844 7.72885 1.38573 7.56512L9.7763 2.22566C9.91279 2.13881 10.0872 2.13881 10.2237 2.22566Z', fill: 'currentColor', }), h('path', { d: 'M2.1637 11.5717C1.96752 11.4515 1.71097 11.513 1.59069 11.7092L1.15509 12.4196C1.03481 12.6158 1.09633 12.8723 1.29251 12.9926L8.9218 17.6705C9.45711 17.9987 10.1262 17.9987 10.6615 17.6705L18.2908 12.9926C18.487 12.8723 18.5485 12.6158 18.4282 12.4196L17.9926 11.7092C17.8723 11.513 17.6158 11.4515 17.4196 11.5717L9.90055 16.182C9.83373 16.223 9.74957 16.223 9.68275 16.182L2.1637 11.5717Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-user-chat': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M426.666667 512a213.333333 213.333333 0 1 1 0.085333-426.752A213.333333 213.333333 0 0 1 426.666667 512z m0-85.333333a128 128 0 0 0 0-256 128 128 0 0 0 0 256z m-384 384a256 256 0 0 1 256-256h256a256 256 0 0 1 256 256v108.330666c0 23.552-19.2 42.666667-42.666667 42.666667H85.333333c-23.466667 0-42.666667-19.114667-42.666666-42.666667V810.666667z m682.666666 0a170.666667 170.666667 0 0 0-170.666666-170.666667H298.666667a170.666667 170.666667 0 0 0-170.666667 170.666667v65.664h597.333333V810.666667z m21.333334-426.666667h213.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z m128 170.666667h85.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-user-chat-active': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M213.333333 298.666667a213.333333 213.333333 0 1 0 426.752-0.085334A213.333333 213.333333 0 0 0 213.333333 298.666667zM298.666667 554.666667a256 256 0 0 0-256 256v108.330666c0 23.552 19.2 42.666667 42.666666 42.666667h682.666667c23.466667 0 42.666667-19.114667 42.666667-42.666667V810.666667a256 256 0 0 0-256-256H298.666667zM960 384h-213.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h213.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333zM960 554.666667h-85.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h85.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-resource-management': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M1.25 3.33335C1.25 2.41288 1.99619 1.66669 2.91667 1.66669H7.91667C8.16398 1.66669 8.39852 1.77654 8.55685 1.96653L10.3903 4.16669H17.0833C18.0038 4.16669 18.75 4.91287 18.75 5.83335V9.08335C18.75 9.3595 18.5261 9.58335 18.25 9.58335H17.5833C17.3072 9.58335 17.0833 9.3595 17.0833 9.08335V5.83335H10C9.75268 5.83335 9.51814 5.7235 9.35982 5.53351L7.52635 3.33335H2.91667V16.6667H8.66667C8.94281 16.6667 9.16667 16.8905 9.16667 17.1667C9.16667 17.3889 9.16667 17.6111 9.16667 17.8334C9.16667 18.1095 8.94281 18.3334 8.66667 18.3334H2.91667C1.9962 18.3334 1.25 17.5872 1.25 16.6667V3.33335Z', fill: 'currentColor', }), h('path', { d: 'M10.9148 16.7795C10.9584 16.829 11.0239 16.8533 11.0895 16.8461L12.2101 16.7242C12.4809 16.6948 12.7397 16.8442 12.8496 17.0935L13.3048 18.1263C13.3315 18.1868 13.3853 18.2314 13.4501 18.2444C13.742 18.3027 14.0439 18.3334 14.353 18.3334C14.662 18.3334 14.9639 18.3027 15.2558 18.2444C15.3207 18.2314 15.3745 18.1868 15.4012 18.1263L15.8564 17.0935C15.9663 16.8442 16.225 16.6948 16.4959 16.7242L17.6164 16.8461C17.6821 16.8533 17.7475 16.829 17.7912 16.7795C18.1889 16.3281 18.4992 15.7978 18.6956 15.2151C18.7167 15.1525 18.705 15.0838 18.666 15.0305L17.9991 14.1191C17.8382 13.8993 17.8382 13.6007 17.9991 13.3809L18.666 12.4696C18.705 12.4163 18.7167 12.3475 18.6956 12.2849C18.4992 11.7022 18.1889 11.1719 17.7912 10.7206C17.7475 10.671 17.6821 10.6468 17.6164 10.6539L16.4959 10.7758C16.225 10.8053 15.9663 10.6559 15.8564 10.4065L15.4012 9.37373C15.3745 9.31319 15.3207 9.26862 15.2558 9.25565C14.9639 9.1973 14.662 9.16669 14.353 9.16669C14.0439 9.16669 13.742 9.1973 13.4501 9.25565C13.3853 9.26862 13.3315 9.31319 13.3048 9.37373L12.8496 10.4065C12.7397 10.6559 12.4809 10.8053 12.2101 10.7758L11.0895 10.6539C11.0239 10.6468 10.9584 10.671 10.9148 10.7206C10.517 11.1719 10.2067 11.7022 10.0104 12.2849C9.98927 12.3475 10.001 12.4163 10.04 12.4696L10.7069 13.3809C10.8677 13.6007 10.8677 13.8993 10.7069 14.1191L10.04 15.0305C10.001 15.0838 9.98927 15.1525 10.0104 15.2151C10.2067 15.7978 10.517 16.3281 10.9148 16.7795ZM16.0196 13.75C16.0196 14.6705 15.2735 15.4167 14.353 15.4167C13.4325 15.4167 12.6863 14.6705 12.6863 13.75C12.6863 12.8295 13.4325 12.0834 14.353 12.0834C15.2735 12.0834 16.0196 12.8295 16.0196 13.75Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-agent': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M448 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333H341.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v85.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h85.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-85.333334zM704 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333h-85.333334a21.333333 21.333333 0 0 0-21.333333 21.333333v85.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h85.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-85.333334z', fill: 'currentColor', }), h('path', { d: 'M426.666667 64a21.333333 21.333333 0 0 1 21.333333-21.333333h128a21.333333 21.333333 0 0 1 21.333333 21.333333V170.666667h-42.666666v85.333333h234.666666a85.333333 85.333333 0 0 1 85.333334 85.333333v469.333334a85.333333 85.333333 0 0 1-85.333334 85.333333h-554.666666a85.333333 85.333333 0 0 1-85.333334-85.333333V341.333333a85.333333 85.333333 0 0 1 85.333334-85.333333H469.333333V170.666667h-42.666666V64zM234.666667 341.333333v469.333334h554.666666V341.333333h-554.666666zM0 490.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v170.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-170.666666zM938.666667 490.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v170.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-170.666666z', fill: 'currentColor', }), ], ), ]) }, }, 'app-agent-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M597.333333 106.666667a21.333333 21.333333 0 0 0-21.333333-21.333334h-128a21.333333 21.333333 0 0 0-21.333333 21.333334V213.333333h42.666666v85.333334H234.666667a85.333333 85.333333 0 0 0-85.333334 85.333333v469.333333a85.333333 85.333333 0 0 0 85.333334 85.333334h554.666666a85.333333 85.333333 0 0 0 85.333334-85.333334V384a85.333333 85.333333 0 0 0-85.333334-85.333333H554.666667V213.333333h42.666666V106.666667z m-298.666666 469.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v85.333333a21.333333 21.333333 0 0 1-21.333334 21.333334h-85.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-85.333333z m405.333333-21.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333334h-85.333333a21.333333 21.333333 0 0 1-21.333334-21.333334v-85.333333a21.333333 21.333333 0 0 1 21.333334-21.333333h85.333333zM85.333333 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v170.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-170.666667zM938.666667 533.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v170.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-170.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-knowledge': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M341.333333 106.666667c69.802667 0 131.754667 33.536 170.666667 85.333333a212.992 212.992 0 0 1 170.666667-85.333333h234.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V768a42.666667 42.666667 0 0 1-42.666667 42.666667H640a85.333333 85.333333 0 0 0-85.333333 85.333333 42.666667 42.666667 0 1 1-85.333334 0 85.333333 85.333333 0 0 0-85.333333-85.333333H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V149.333333a42.666667 42.666667 0 0 1 42.666667-42.666666H341.333333zM149.333333 725.333333H384a169.813333 169.813333 0 0 1 85.333333 22.869334V320a128 128 0 0 0-128-128H149.333333V725.333333zM682.666667 192a128 128 0 0 0-128 128v428.202667A169.813333 169.813333 0 0 1 640 725.333333h234.666667V192H682.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-knowledge-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M341.333333 106.666667c69.802667 0 131.754667 33.536 170.666667 85.333333a212.992 212.992 0 0 1 170.666667-85.333333h234.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V768a42.666667 42.666667 0 0 1-42.666667 42.666667H640a85.333333 85.333333 0 0 0-85.333333 85.333333 42.666667 42.666667 0 1 1-85.333334 0 85.333333 85.333333 0 0 0-85.333333-85.333333H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V149.333333a42.666667 42.666667 0 0 1 42.666667-42.666666H341.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-tool': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M277.333333 320H170.666667v128h106.666666v-42.666667h85.333334v42.666667h298.666666v-42.666667h85.333334v42.666667H853.333333v-128H277.333333z m0-85.333333v-85.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h384a42.666667 42.666667 0 0 1 42.666667 42.666666v85.333334H896a42.666667 42.666667 0 0 1 42.666667 42.666666v597.333334a42.666667 42.666667 0 0 1-42.666667 42.666666H128a42.666667 42.666667 0 0 1-42.666667-42.666666v-597.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h149.333333z m85.333334 0h298.666666v-42.666667h-298.666666v42.666667z m298.666666 298.666666h-298.666666v42.666667h-85.333334v-42.666667H170.666667v298.666667h682.666666v-298.666667h-106.666666v42.666667h-85.333334v-42.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-tool-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M277.333333 149.333333v85.333334H128a42.666667 42.666667 0 0 0-42.666667 42.666666V426.666667h213.333334V384h85.333333v42.666667h256V384h85.333333v42.666667h213.333334V277.333333a42.666667 42.666667 0 0 0-42.666667-42.666666h-149.333333v-85.333334a42.666667 42.666667 0 0 0-42.666667-42.666666h-384a42.666667 42.666667 0 0 0-42.666667 42.666666z m384 85.333334h-298.666666v-42.666667h298.666666v42.666667z', fill: 'currentColor', }), h('path', { d: 'M938.666667 512h-213.333334v42.666667h-85.333333v-42.666667H384v42.666667H298.666667v-42.666667H85.333333v362.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h768a42.666667 42.666667 0 0 0 42.666667-42.666666V512z', fill: 'currentColor', }), ], ), ]) }, }, 'app-model': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M42.666667 326.144a42.666667 42.666667 0 0 1 25.002666-38.826667l426.666667-193.962666a42.666667 42.666667 0 0 1 35.328 0l426.666667 193.92a42.666667 42.666667 0 0 1 25.002666 38.826666v415.530667a42.666667 42.666667 0 0 1-23.594666 38.144l-426.666667 213.333333a42.666667 42.666667 0 0 1-38.144 0l-426.666667-213.333333A42.666667 42.666667 0 0 1 42.666667 741.632V326.144z m777.301333-7.082667L512 179.072 202.368 319.786667l307.925333 132.693333 309.674667-133.418667zM554.666667 526.250667v359.68l341.333333-170.666667V379.221333l-341.333333 147.029334zM128 380.672v334.592l341.333333 170.666667v-358.229334L128 380.672z', fill: 'currentColor', }), ], ), ]) }, }, 'app-model-active': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M0.666748 6.07278C0.666748 5.71259 1.0361 5.47056 1.36635 5.61435L6.95893 8.04931C7.14136 8.12874 7.25933 8.30877 7.25933 8.50774V14.5055C7.25933 14.8817 6.85964 15.1231 6.5267 14.9481L0.915073 11.9985C0.840862 11.9578 0.778897 11.8989 0.735342 11.8276C0.691787 11.7564 0.668161 11.6753 0.666813 11.5924L0.666748 11.585V6.07278ZM14.6312 5.60774C14.9618 5.46158 15.3334 5.70361 15.3334 6.06503V11.585C15.3334 11.6691 15.3104 11.7518 15.2668 11.8244C15.2231 11.8971 15.1604 11.9571 15.0851 11.9985L9.47345 14.9481C9.14051 15.1231 8.74081 14.8817 8.74082 14.5055L8.74083 8.53793C8.74083 8.33999 8.8576 8.16069 9.03863 8.08064L14.6312 5.60774ZM7.76 1.39457C7.83327 1.35437 7.91597 1.33325 8.00008 1.33325C8.0842 1.33325 8.16689 1.35437 8.24016 1.39457L13.55 3.75304C13.9482 3.92991 13.9454 4.49602 13.5455 4.66894L8.19851 6.98075C8.07189 7.0355 7.92827 7.0355 7.80165 6.98075L2.45469 4.66894C2.05476 4.49602 2.05196 3.92991 2.45016 3.75304L7.76 1.39457Z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/system.ts ================================================ import { h } from 'vue' export default { 'app-add-users': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 20 20', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M6.24984 5.41667C6.24984 6.7975 7.37067 7.91667 8.74984 7.91667C10.129 7.91667 11.2498 6.7975 11.2498 5.41667C11.2498 4.03583 10.129 2.91667 8.74984 2.91667C7.37067 2.91667 6.24984 4.03583 6.24984 5.41667ZM8.74984 1.25C11.0498 1.25 12.9165 3.11542 12.9165 5.41667C12.9165 7.71792 11.0498 9.58333 8.74984 9.58333C6.44984 9.58333 4.58317 7.71792 4.58317 5.41667C4.58317 3.11542 6.44984 1.25 8.74984 1.25ZM3.43734 15C3.37067 15.2663 3.33317 15.5454 3.33317 15.8333V16.6667H10.854C11.0841 16.6667 11.2706 16.8532 11.2706 17.0833V17.9167C11.2706 18.1468 11.0841 18.3333 10.854 18.3333H2.49984C2.0415 18.3333 1.6665 17.9604 1.6665 17.5V15.8333C1.6665 13.0721 3.904 10.8333 6.6665 10.8333H10.854C11.0841 10.8333 11.2706 11.0199 11.2706 11.25V12.0833C11.2706 12.3135 11.0841 12.5 10.854 12.5H6.6665C5.11234 12.5 3.80817 13.5625 3.43734 15ZM15.4165 11.6667C15.6466 11.6667 15.8332 11.8532 15.8332 12.0833V14.1667H17.9165C18.1466 14.1667 18.3332 14.3532 18.3332 14.5833V15.4167C18.3332 15.6468 18.1466 15.8333 17.9165 15.8333H15.8332V17.9167C15.8332 18.1468 15.6466 18.3333 15.4165 18.3333H14.5832C14.3531 18.3333 14.1665 18.1468 14.1665 17.9167V15.8333H12.0832C11.8531 15.8333 11.6665 15.6468 11.6665 15.4167V14.5833C11.6665 14.3532 11.8531 14.1667 12.0832 14.1667H14.1665V12.0833C14.1665 11.8532 14.3531 11.6667 14.5832 11.6667H15.4165Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-delete-users': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M661.333333 277.333333a213.333333 213.333333 0 1 0-426.752 0.085334A213.333333 213.333333 0 0 0 661.333333 277.333333z m-213.333333 128a128.042667 128.042667 0 0 1 0-256 128.042667 128.042667 0 0 1 0 256zM170.666667 810.666667c0-14.762667 1.92-29.013333 5.333333-42.666667 18.986667-73.6 85.76-128 165.333333-128h171.733334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333H341.333333a256 256 0 0 0-256 256v85.333333c0 23.552 19.2 42.666667 42.666667 42.666667h385.066667a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334H170.666667v-42.666666zM776.405333 663.893333l62.634667 62.677334H618.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h220.928l-63.189334 63.189333a21.333333 21.333333 0 0 0 0 30.165334l30.165334 30.208a21.333333 21.333333 0 0 0 30.165333 0l150.826667-150.869334a21.333333 21.333333 0 0 0 0-30.165333l-150.826667-150.869333a21.333333 21.333333 0 0 0-30.165333 0l-30.165334 30.208a21.333333 21.333333 0 0 0 0 30.165333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-admin-operation': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M805.290667 298.666667a170.752 170.752 0 0 1-330.581334 0H112.682667c-9.514667 0-12.970667-1.024-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.106667C86.357333 284.330667 85.333333 280.832 85.333333 271.36V240.64c0-9.472 0.981333-12.928 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.064C99.712 214.314667 103.168 213.333333 112.64 213.333333h362.026667a170.752 170.752 0 0 1 330.581333 0h106.026667c9.514667 0 12.970667 0.981333 16.426666 2.816a19.370667 19.370667 0 0 1 8.106667 8.106667c1.834667 3.413333 2.816 6.912 2.816 16.384v30.677333c0 9.472-0.981333 12.928-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.064c-3.456 1.834667-6.912 2.858667-16.426666 2.858667h-106.026667zM640 341.333333a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666zM549.290667 810.666667a170.752 170.752 0 0 1-330.581334 0H112.682667c-9.514667 0-12.970667-1.024-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.106667c-1.834667-3.413333-2.816-6.912-2.816-16.384v-30.677333c0-9.472 0.981333-12.928 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.064c3.456-1.834667 6.912-2.816 16.426667-2.816h106.026666a170.752 170.752 0 0 1 330.581334 0h362.026666c9.514667 0 12.970667 0.981333 16.426667 2.816a19.370667 19.370667 0 0 1 8.106667 8.106667c1.834667 3.413333 2.816 6.912 2.816 16.384v30.634667c0 9.514667-0.981333 12.970667-2.858667 16.469333a19.370667 19.370667 0 0 1-8.064 8.064c-3.456 1.834667-6.912 2.858667-16.426667 2.858667h-362.026666zM384 853.333333a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666z', fill: 'currentColor', }), ], ), ]) }, }, 'app-operate-log': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M213.333333 128v768h597.333334V128H213.333333zM170.666667 42.666667h682.666666c23.552 0 42.666667 20.010667 42.666667 44.714666v849.237334c0 24.704-19.114667 44.714667-42.666667 44.714666H170.666667c-23.552 0-42.666667-20.010667-42.666667-44.714666V87.381333C128 62.677333 147.114667 42.666667 170.666667 42.666667z m149.333333 256h170.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-170.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z m0 170.666666h384a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-384a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334z m0 170.666667h384a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-384a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-resource-mapping': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M3.64023 7.55547C4.16284 6.83981 4.62542 5.57247 5.08688 3.7308C5.35033 2.67938 5.67074 2.22214 5.94869 2.22214C6.19415 2.22214 6.39314 2.02316 6.39314 1.7777C6.39314 1.53224 6.19415 1.33325 5.94869 1.33325C5.11916 1.33325 4.57762 2.10607 4.22465 3.51476C3.8289 5.09417 3.42652 6.20022 3.07385 6.81123C2.9227 6.71944 2.74528 6.66658 2.55552 6.66658H1.88886C1.33657 6.66658 0.888855 7.1143 0.888855 7.66658V8.33325C0.888855 8.88554 1.33657 9.33325 1.88886 9.33325H2.55552C2.72067 9.33325 2.87647 9.29322 3.01375 9.22232C3.33858 9.88602 3.69875 10.9575 4.05283 12.4188C4.40498 13.8722 4.9433 14.6666 5.7777 14.6666C6.02316 14.6666 6.22214 14.4676 6.22214 14.2221C6.22214 13.9767 6.02316 13.7777 5.7777 13.7777C5.50461 13.7777 5.18098 13.3001 4.91672 12.2095C4.49159 10.455 4.06638 9.20685 3.59354 8.44436H6.46662C6.57707 8.44436 6.66662 8.35482 6.66662 8.24436V7.75547C6.66662 7.64502 6.57707 7.55547 6.46662 7.55547H3.64023Z', fill: '#646A73', }), h('path', { d: 'M7.99998 2.11103C7.99998 1.92694 8.14922 1.7777 8.33332 1.7777H14.7778C14.9619 1.7777 15.1111 1.92693 15.1111 2.11103V3.22214C15.1111 3.40624 14.9619 3.55547 14.7778 3.55547H8.33332C8.14922 3.55547 7.99998 3.40624 7.99998 3.22214V2.11103Z', fill: '#646A73', }), h('path', { d: 'M8.33332 7.11103C8.14922 7.11103 7.99998 7.26027 7.99998 7.44436V8.55547C7.99998 8.73957 8.14922 8.88881 8.33332 8.88881H14.7778C14.9619 8.88881 15.1111 8.73957 15.1111 8.55547V7.44436C15.1111 7.26027 14.9619 7.11103 14.7778 7.11103H8.33332Z', fill: '#646A73', }), h('path', { d: 'M7.99998 12.7777C7.99998 12.5936 8.14922 12.4444 8.33332 12.4444H14.7778C14.9619 12.4444 15.1111 12.5936 15.1111 12.7777V13.8888C15.1111 14.0729 14.9619 14.2221 14.7778 14.2221H8.33332C8.14922 14.2221 7.99998 14.0729 7.99998 13.8888V12.7777Z', fill: '#646A73', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/tool.ts ================================================ import { h } from 'vue' export default { 'app-tool-store': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M836.992 85.333333l6.485333 0.512a42.666667 42.666667 0 0 1 33.28 26.794667l64.042667 165.76c26.965333 69.845333 5.034667 142.634667-44.117333 188.074667 0.085333 0.938667 0.170667 1.877333 0.170666 2.858666v426.666667a42.666667 42.666667 0 0 1-42.666666 42.666667h-682.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V469.333333c0-0.938667 0.042667-1.877333 0.128-2.773333C79.786667 421.12 57.856 348.330667 84.821333 278.528L148.906667 112.64l2.773333-5.888A42.666667 42.666667 0 0 1 188.672 85.333333h648.32z m-185.301333 368.725334A170.24 170.24 0 0 1 523.648 512h-21.76a170.24 170.24 0 0 1-128.085333-57.941333 170.922667 170.922667 0 0 1-159.616 55.04V853.333333h597.333333v-344.192a171.050667 171.050667 0 0 1-159.829333-55.082666z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/icons/trigger.ts ================================================ import { h } from 'vue' export default { 'app-schedule-report': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M840.832 55.168A42.666667 42.666667 0 0 1 853.333333 85.333333v341.333334h-85.333333V128H170.666667v768h213.333333v85.333333H128a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h682.666667a42.666667 42.666667 0 0 1 30.165333 12.501333z', fill: 'currentColor', }), h('path', { d: 'M277.333333 256a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h384a21.333333 21.333333 0 0 0 21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333334-21.333333h-384zM256 448a21.333333 21.333333 0 0 1 21.333333-21.333333h170.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-170.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667zM758.741333 734.592h78.378667a10.666667 10.666667 0 0 1 10.666667 10.666667v45.482666a10.666667 10.666667 0 0 1-10.666667 10.666667h-134.528a10.666667 10.666667 0 0 1-10.666667-10.666667V656.213333a10.666667 10.666667 0 0 1 10.666667-10.666666h45.482667a10.666667 10.666667 0 0 1 10.666666 10.666666v78.378667z', fill: 'currentColor', }), h('path', { d: 'M469.333333 768a256 256 0 1 0 512 0 256 256 0 0 0-512 0z m376.661334 120.661333a170.666667 170.666667 0 1 1-241.322667-241.322666 170.666667 170.666667 0 0 1 241.322667 241.322666z', fill: 'currentColor', }), ], ), ]) }, }, } ================================================ FILE: ui/src/components/app-icon/index.ts ================================================ import { h } from 'vue' const iconsImport: any = import.meta.glob('./icons/*.ts', { eager: true, import: 'default' }) const dynamicIcons = Object.values(iconsImport).reduce( (acc: Record, module) => ({ ...acc, ...(typeof module === 'object' && module !== null ? module : {}), }), {} as Record, ) export const iconMap: any = { 'app-warning': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 234.666667A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM522.666667 384h-64a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h21.333333v213.333334H426.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h192a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-53.333334v-256a42.666667 42.666667 0 0 0-42.666666-42.666667z', fill: 'currentColor', }), h('path', { d: 'M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z', fill: 'currentColor', }), ], ), ]) }, }, 'app-warning-colorful': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M42.666667 512c0 259.2 210.133333 469.333333 469.333333 469.333333s469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667 42.666667 252.8 42.666667 512z m469.333333-277.333333A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM458.666667 384h64a42.666667 42.666667 0 0 1 42.666666 42.666667v256h53.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H426.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h53.333333v-213.333334h-21.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z', fill: '#3370FF', }), ], ), ]) }, }, 'app-copy': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M213.333333 341.333333v512h426.666667V341.333333H213.333333z m512-42.666666v602.069333c0 20.949333-17.834667 37.930667-39.808 37.930667H167.808C145.834667 938.666667 128 921.685333 128 900.736V293.973333C128 272.981333 145.834667 256 167.808 256H682.666667a42.666667 42.666667 0 0 1 42.666666 42.666667z m158.165334-200.832A42.538667 42.538667 0 0 1 896 128v533.333333a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334V170.666667H405.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H853.333333c11.776 0 22.442667 4.778667 30.165334 12.501334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-magnify': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M366.165333 593.749333a21.333333 21.333333 0 0 1 30.208 0l30.165334 30.165334a21.333333 21.333333 0 0 1 0 30.208l-170.752 170.666666H377.173333a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334H156.458667a42.538667 42.538667 0 0 1-42.666667-42.666667v-220.16a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v113.493333l167.04-167.04z m500.992-480a42.538667 42.538667 0 0 1 42.666667 42.666667v220.16a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-113.493333l-167.04 167.04a21.333333 21.333333 0 0 1-30.165334 0l-30.165333-30.165334a21.333333 21.333333 0 0 1 0-30.165333l170.709333-170.666667h-121.344a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h220.672z', fill: 'currentColor', }), ], ), ]) }, }, 'app-minify': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M384.341333 597.205333a42.538667 42.538667 0 0 1 42.666667 42.666667v220.16a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-113.493333l-167.04 167.04a21.333333 21.333333 0 0 1-30.165334 0l-30.165333-30.208a21.333333 21.333333 0 0 1 0-30.165334l170.709333-170.666666H163.669333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334h220.672zM849.92 110.506667a21.333333 21.333333 0 0 1 30.165333 0l30.165334 30.165333a21.333333 21.333333 0 0 1 0 30.165333l-170.709334 170.666667h121.344a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-220.672a42.538667 42.538667 0 0 1-42.666666-42.666666v-220.16a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v113.493333l167.04-166.997333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-disabled': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 21.333333C241.024 21.333333 21.333333 241.024 21.333333 512S241.024 1002.666667 512 1002.666667 1002.666667 782.976 1002.666667 512 782.976 21.333333 512 21.333333z m297.685333 697.856L304.810667 214.314667a362.666667 362.666667 0 0 1 504.874666 504.874666zM149.333333 512c0-77.056 24.021333-148.48 64.981334-207.189333l504.874666 504.874666A362.666667 362.666667 0 0 1 149.333333 512z', fill: 'currentColor', }), ], ), ]) }, }, 'app-go': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M2.66671 4.66665V13.3333H13.3334V8.66665H14.6667V14C14.6667 14.3682 14.3682 14.6666 14 14.6666H2.00004C1.63185 14.6666 1.33337 14.3682 1.33337 14V3.99998C1.33337 3.63179 1.63185 3.33331 2.00004 3.33331H7.33337V4.66665H2.66671Z', fill: 'currentColor', }), h('path', { d: 'M14.6665 1.99998V6.66665H13.3332V3.60931L9.34987 7.59265C9.28736 7.65514 9.20259 7.69024 9.11421 7.69024C9.02582 7.69024 8.94105 7.65514 8.87854 7.59265L8.40721 7.12131C8.34472 7.0588 8.30961 6.97403 8.30961 6.88565C8.30961 6.79726 8.34472 6.71249 8.40721 6.64998L12.3905 2.66665H9.33321V1.33331H13.9999C14.1767 1.33331 14.3463 1.40355 14.4713 1.52858C14.5963 1.6536 14.6665 1.82317 14.6665 1.99998Z', fill: 'currentColor', }), ], ), ]) }, }, 'right-outlined': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 12 12', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M8.13909 6L4.07322 1.93414C3.97559 1.83651 3.97559 1.67822 4.07322 1.58059L4.42678 1.22703C4.52441 1.1294 4.6827 1.1294 4.78033 1.22703L9.19975 5.64645C9.39501 5.84171 9.39501 6.15829 9.19975 6.35356L4.78033 10.773C4.6827 10.8706 4.52441 10.8706 4.42678 10.773L4.07322 10.4194C3.97559 10.3218 3.97559 10.1635 4.07322 10.0659L8.13909 6Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-migrate': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M729.002667 42.666667a42.752 42.752 0 0 1 30.165333 12.501333l124.330667 124.416a42.624 42.624 0 0 1 12.501333 30.122667V512h-85.333333V256.853333h-106.666667a21.333333 21.333333 0 0 1-21.333333-21.333333V128H213.333333v768h213.333334v85.333333H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h558.336z', fill: 'currentColor', }), h('path', { d: 'M731.178667 603.562667a21.12 21.12 0 0 1 29.994666 0l165.077334 165.973333c16.597333 16.64 16.597333 43.690667 0 60.330667l-165.12 165.930666a21.12 21.12 0 0 1-29.952 0l-30.037334-30.165333a21.418667 21.418667 0 0 1 0-30.165333l89.856-90.325334-258.389333-1.706666a21.333333 21.333333 0 0 1-21.12-21.248l-0.170667-40.448a21.290667 21.290667 0 0 1 21.12-21.418667h0.213334l266.154666 1.749333-97.706666-98.133333a21.418667 21.418667 0 0 1 0-30.165333l30.08-30.165334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-export': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M791.04 554.24l-386.432-1.728a21.248 21.248 0 0 1-21.12-21.248L383.36 490.88c-0.064-11.776 9.408-21.376 21.12-21.44h0.192l394.112 1.728-97.664-98.112a21.44 21.44 0 0 1 0-30.208l30.08-30.144a21.12 21.12 0 0 1 29.952 0l165.12 165.952a42.88 42.88 0 0 1 0 60.288l-165.12 165.952a21.12 21.12 0 0 1-30.016 0l-30.016-30.144a21.44 21.44 0 0 1 0-30.208L791.04 554.24z m-132.672-383.552H170.24v682.624h488.128c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376h-530.56A42.56 42.56 0 0 1 85.376 896V128c0-23.552 19.008-42.688 42.496-42.688h530.56c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376z', fill: 'currentColor', }), ], ), ]) }, }, 'app-import': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M519.381333 554.24l92.416 90.325333c8.533333 8.32 8.533333 21.845333 0 30.165334l-30.848 30.165333a22.186667 22.186667 0 0 1-30.890666 0L411.178667 569.173333l-30.890667-30.165333a41.984 41.984 0 0 1 0-60.330667l169.813333-165.973333a22.186667 22.186667 0 0 1 30.848 0l30.848 30.208c8.533333 8.32 8.533333 21.845333 0 30.165333l-100.437333 98.133334 405.376-1.706667h0.213333c12.032 0 21.76 9.642667 21.717334 21.418667l-0.170667 40.405333a21.589333 21.589333 0 0 1-21.76 21.248l-397.354667 1.706667zM674.688 170.666667H172.629333v682.666666h502.058667c12.032 0 21.802667 9.557333 21.802667 21.333334v42.666666c0 11.776-9.770667 21.333333-21.845334 21.333334H129.024A43.178667 43.178667 0 0 1 85.333333 896V128c0-23.552 19.541333-42.666667 43.648-42.666667h545.706667c12.032 0 21.802667 9.557333 21.802667 21.333334v42.666666c0 11.776-9.770667 21.333333-21.845334 21.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-download': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M14 12.3333V14C14 14.3681 13.7015 14.6666 13.3333 14.6666H2.66667C2.29848 14.6666 2 14.3681 2 14V12.3333C2 12.1492 2.14924 12 2.33333 12H3C3.18409 12 3.33333 12.1492 3.33333 12.3333V13.3333H12.6667V12.3333C12.6667 12.1492 12.8159 12 13 12H13.6667C13.8508 12 14 12.1492 14 12.3333ZM8.66667 9.3571L10.6736 7.35013C10.8038 7.21995 11.0149 7.21995 11.1451 7.35013L11.6165 7.82153C11.7466 7.9517 11.7466 8.16276 11.6165 8.29293L8.31663 11.5928C8.25154 11.6579 8.16623 11.6904 8.08092 11.6904C7.99562 11.6904 7.91031 11.6579 7.84522 11.5928L4.54539 8.29293C4.41521 8.16276 4.41521 7.9517 4.54539 7.82153L5.01679 7.35013C5.14697 7.21995 5.35802 7.21995 5.4882 7.35013L7.33334 9.19526V1.99996C7.33334 1.81586 7.48257 1.66663 7.66667 1.66663H8.33334C8.51743 1.66663 8.66667 1.81586 8.66667 1.99996V9.3571Z', fill: 'currentColor', }), ], ), ]) }, }, 'app-upload': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M896 789.333333V896a42.666667 42.666667 0 0 1-42.666667 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667v-106.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333V853.333333h597.333334v-64a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333z m-341.333333-512l128.426666 128.426667a21.333333 21.333333 0 0 0 30.208 0l30.165334-30.165333a21.333333 21.333333 0 0 0 0-30.165334l-211.2-211.2a21.248 21.248 0 0 0-30.165334 0l-211.2 211.2a21.333333 21.333333 0 0 0 0 30.165334l30.165334 30.165333a21.333333 21.333333 0 0 0 30.165333 0L469.333333 287.701333v460.501334a21.333333 21.333333 0 0 0 21.333334 21.333333h42.666666a21.333333 21.333333 0 0 0 21.333334-21.333333V277.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-404': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', style: 'height:14px;width:14px', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M260.266667 789.333333c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333H38.4c-12.8 0-29.866667-8.533333-34.133333-21.333333-4.266667-17.066667-4.266667-29.866667 4.266666-42.666667l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4H298.666667v59.733333c0 21.333333-17.066667 38.4-38.4 38.4z m-145.066667-179.2h106.666667V469.333333l-106.666667 140.8zM913.066667 742.4c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333h-183.466667c-12.8 0-29.866667-8.533333-34.133333-21.333334-8.533333-12.8-4.266667-29.866667 4.266666-38.4l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4h-34.133333v59.733334c0 17.066667-17.066667 34.133333-38.4 34.133333zM768 567.466667h106.666667V426.666667L768 567.466667zM533.333333 597.333333c-46.933333 0-85.333333-25.6-119.466666-68.266666-29.866667-38.4-42.666667-93.866667-42.666667-145.066667 0-55.466667 17.066667-106.666667 42.666667-145.066667 29.866667-42.666667 72.533333-68.266667 119.466666-68.266666 46.933333 0 85.333333 25.6 119.466667 68.266666 29.866667 38.4 42.666667 93.866667 42.666667 145.066667 0 55.466667-17.066667 106.666667-42.666667 145.066667-34.133333 46.933333-76.8 68.266667-119.466667 68.266666z m0-362.666666c-55.466667 0-98.133333 68.266667-98.133333 149.333333s46.933333 149.333333 98.133333 149.333333c55.466667 0 98.133333-68.266667 98.133334-149.333333s-46.933333-149.333333-98.133334-149.333333z', fill: '#978CFF', }), h('path', { d: 'M354.133333 691.2a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z', fill: '#E3E5FC', }), h('path', { d: 'M8.533333 832a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z', fill: '#E3E5FC', }), h('path', { d: 'M661.333333 797.866667a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z', fill: '#E3E5FC', }), ], ), ]) }, }, 'app-edit': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M524.032 239.701333l85.973333 85.973334 63.786667-63.829334-86.314667-86.784-63.445333 64.64z m25.685333 146.346667l-85.418666-85.418667-292.266667 297.984v0.128l82.56 82.56h0.170667l294.954666-295.253333z m199.68-77.226667l0.256 0.256L290.730667 768H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-162.730666l443.306667-446.72-0.426667-0.426667 30.08-30.037333a42.666667 42.666667 0 0 1 60.330667 0l0.085333 0.042666 146.517334 147.328a42.666667 42.666667 0 0 1-0.085334 60.245334l-15.786666 15.786666zM106.666667 853.333333h810.666666a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-810.666666a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-delete': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M341.333333 170.666667V128a42.666667 42.666667 0 0 1 42.666667-42.666667h256a42.666667 42.666667 0 0 1 42.666667 42.666667v42.666667h228.650666c9.514667 0 12.970667 0.981333 16.426667 2.858666a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667v30.634667c0 9.514667-0.981333 12.970667-2.858667 16.426666a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816H853.333333v640a42.666667 42.666667 0 0 1-42.666666 42.666667H213.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V256H112.682667c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064C86.357333 241.621333 85.333333 238.165333 85.333333 228.693333v-30.634666c0-9.514667 0.981333-12.970667 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.106667C99.712 171.690667 103.168 170.666667 112.64 170.666667H341.333333zM256 256v597.333333h512V256H256z m149.333333 85.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v384a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-384a21.333333 21.333333 0 0 1 21.333333-21.333334z m170.666667 0h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v384a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-384a21.333333 21.333333 0 0 1 21.333333-21.333334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-more': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M768 448h85.333333a21.248 21.248 0 0 1 21.333334 21.333333v85.333334a21.248 21.248 0 0 1-21.333334 21.333333h-85.333333a21.333333 21.333333 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z m-597.333333 0h85.333333a21.290667 21.290667 0 0 1 21.333333 21.333333v85.333334a21.333333 21.333333 0 0 1-21.333333 21.333333H170.666667a21.290667 21.290667 0 0 1-21.333334-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333334-21.333333z m298.666666 0h85.333334a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-key': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 512a85.333333 85.333333 0 0 1 42.666667 159.232V746.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-75.434667A85.333333 85.333333 0 0 1 512 512z', fill: 'currentColor', }), h('path', { d: 'M512 85.333333c129.578667 0 234.666667 104.96 234.666667 234.666667V341.333333H896c23.552 0 42.666667 19.2 42.666667 42.666667v512c0 23.466667-19.114667 42.666667-42.666667 42.666667H128c-23.594667 0-42.666667-19.2-42.666667-42.666667V384c0-23.466667 19.072-42.666667 42.666667-42.666667h149.333333v-21.333333C277.333333 190.293333 382.421333 85.333333 512 85.333333zM170.666667 853.333333h682.666666V426.666667H170.666667v426.666666z m341.333333-682.666666a149.290667 149.290667 0 0 0-149.333333 149.333333V341.333333h298.666666v-21.333333C661.333333 237.44 594.474667 170.666667 512 170.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-sync': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M295.509333 775.893333a341.333333 341.333333 0 0 0 553.941334-315.562666l-40.149334 23.765333a21.333333 21.333333 0 0 1-31.744-22.869333l30.72-142.72a21.333333 21.333333 0 0 1 26.965334-15.957334l139.818666 41.898667a21.333333 21.333333 0 0 1 4.736 38.826667l-52.394666 30.933333c7.381333 31.402667 11.264 64.128 11.264 97.792 0 235.648-191.018667 426.666667-426.666667 426.666667a425.216 425.216 0 0 1-294.4-117.802667l77.909333-44.970667zM715.392 237.866667a341.333333 341.333333 0 0 0-542.890667 309.930666l46.805334-26.624a21.333333 21.333333 0 0 1 31.317333 23.338667L217.6 686.72a21.333333 21.333333 0 0 1-27.221333 15.488l-139.093334-44.202667a21.333333 21.333333 0 0 1-4.096-38.869333l45.866667-26.112C87.978667 566.784 85.333333 539.690667 85.333333 512 85.333333 276.352 276.352 85.333333 512 85.333333c108.373333 0 207.232 40.362667 282.453333 106.88l-79.061333 45.653334z', fill: 'currentColor', }), ], ), ]) }, }, 'app-generate-question': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M551.850667 369.792c9.386667 0 17.066667 7.637333 17.066666 17.066667v51.2a17.066667 17.066667 0 0 1-17.066666 17.066666h-110.933334c-6.997333 0-12.8 5.034667-14.037333 11.648L426.666667 469.333333v341.333334c0 6.997333 5.034667 12.8 11.690666 13.994666l2.56 0.213334H896c6.997333 0 12.8-4.992 13.994667-11.648l0.256-2.56v-341.333334c0-6.997333-5.034667-12.8-11.690667-13.994666l-2.56-0.213334h-110.933333a17.066667 17.066667 0 0 1-17.066667-17.066666v-51.2a17.066667 17.066667 0 0 1 17.066667-17.066667H896c53.162667 0 96.597333 41.642667 99.413333 94.08l0.170667 5.461333v341.333334a99.541333 99.541333 0 0 1-94.122667 99.413333l-5.461333 0.128H440.917333a99.541333 99.541333 0 0 1-99.413333-94.08L341.333333 810.666667v-341.333334c0-53.162667 41.642667-96.554667 94.122667-99.413333l5.461333-0.128h110.933334z m59.733333-256c53.12 0 96.554667 41.642667 99.413333 94.08l0.128 5.461333v341.333334a99.541333 99.541333 0 0 1-94.122666 99.413333l-5.418667 0.128h-110.933333a17.066667 17.066667 0 0 1-17.066667-17.066667v-51.2c0-9.386667 7.637333-17.066667 17.066667-17.066666h110.933333c6.954667 0 12.8-4.992 13.994667-11.648l0.213333-2.56V213.333333c0-6.997333-5.034667-12.8-11.690667-13.994666l-2.56-0.213334H156.501333c-6.997333 0-12.8 5.034667-13.994666 11.648L142.208 213.333333v341.333334c0 6.997333 5.034667 12.8 11.690667 13.994666l2.56 0.213334h110.933333c9.386667 0 17.066667 7.68 17.066667 17.066666v51.2a17.066667 17.066667 0 0 1-17.066667 17.066667h-110.933333a99.541333 99.541333 0 0 1-99.413334-94.08L56.874667 554.666667V213.333333c0-53.162667 41.685333-96.554667 94.122666-99.413333l5.461334-0.128h455.082666z', fill: 'currentColor', }), ], ), ]) }, }, 'app-lock': { iconReader: () => { return h('i', [ h( 'svg', { viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M277.333333 341.333333v-21.333333C277.333333 190.293333 382.421333 85.333333 512 85.333333s234.666667 104.96 234.666667 234.666667V341.333333H896c23.552 0 42.666667 19.2 42.666667 42.666667v512c0 23.466667-19.114667 42.666667-42.666667 42.666667H128c-23.594667 0-42.666667-19.2-42.666667-42.666667V384c0-23.466667 19.072-42.666667 42.666667-42.666667h149.333333z m384-21.333333C661.333333 237.44 594.474667 170.666667 512 170.666667a149.290667 149.290667 0 0 0-149.333333 149.333333V341.333333h298.666666v-21.333333zM170.666667 426.666667v426.666666h682.666666V426.666667H170.666667z m341.333333 341.333333a128.042667 128.042667 0 0 1 0-256 128.042667 128.042667 0 0 1 0 256z m0-85.333333c23.594667 0 42.666667-19.2 42.666667-42.666667s-19.072-42.666667-42.666667-42.666667c-23.594667 0-42.666667 19.2-42.666667 42.666667s19.072 42.666667 42.666667 42.666667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-operation': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M85.333333 234.666667a149.333333 149.333333 0 0 1 292.48-42.666667H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H377.813333A149.418667 149.418667 0 0 1 85.333333 234.666667z m21.333334 320a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334h262.186666a149.418667 149.418667 0 0 1 286.293334 0H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-262.186666a149.418667 149.418667 0 0 1-286.293334 0H106.666667z m405.333333 21.333333a64 64 0 1 0 0-128 64 64 0 0 0 0 128z m-405.333333 256A21.333333 21.333333 0 0 1 85.333333 810.666667v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h539.52a149.418667 149.418667 0 0 1 292.48 42.666666 149.333333 149.333333 0 0 1-292.48 42.666667H106.666667z m682.666666-106.666667a64 64 0 1 0 0 128 64 64 0 0 0 0-128zM234.666667 298.666667a64 64 0 1 0 0-128 64 64 0 0 0 0 128z', fill: 'currentColor', }), ], ), ]) }, }, 'app-password-hide': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M512 640c-28.032 0-55.466667-2.218667-82.090667-6.4l-21.248 79.274667a21.333333 21.333333 0 0 1-26.154666 15.061333L341.333333 716.885333a21.333333 21.333333 0 0 1-15.061333-26.112l20.821333-77.653333a473.770667 473.770667 0 0 1-97.152-45.653333l-67.84 67.84a21.333333 21.333333 0 0 1-30.122666 0l-30.165334-30.208a21.333333 21.333333 0 0 1 0-30.165334l59.733334-59.733333A386.389333 386.389333 0 0 1 104.789333 416.426667a37.76 37.76 0 0 1 7.594667-45.397334c10.496-9.514667 17.877333-16 24.32-22.442666a170.24 170.24 0 0 0 1.834667-1.92c9.301333-9.6 25.173333-6.016 30.634666 6.186666C222.336 471.936 349.568 554.666667 512 554.666667c155.648 0 285.866667-80.512 338.090667-190.976 1.365333-2.858667 2.901333-6.485333 4.437333-10.325334a18.346667 18.346667 0 0 1 29.866667-6.613333l27.392 27.434667a36.565333 36.565333 0 0 1 6.997333 42.666666c-1.792 3.456-3.541333 6.698667-5.034667 9.301334a390.4 390.4 0 0 1-76.928 94.293333l54.442667 54.485333a21.333333 21.333333 0 0 1 0 30.165334l-30.165333 30.165333a21.333333 21.333333 0 0 1-30.165334 0l-63.658666-63.658667a475.306667 475.306667 0 0 1-90.282667 41.514667l20.778667 77.653333a21.333333 21.333333 0 0 1-15.061334 26.112l-41.216 11.093334a21.333333 21.333333 0 0 1-26.154666-15.104l-21.248-79.317334c-26.581333 4.266667-54.058667 6.442667-82.090667 6.442667z', fill: 'currentColor', }), ], ), ]) }, }, 'app-add-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M469.333333 469.333333V112.682667c0-9.514667 0.981333-12.970667 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.106667c3.456-1.834667 6.912-2.816 16.426667-2.816h30.634666c9.514667 0 12.970667 0.981333 16.426667 2.858667a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667V469.333333h356.650666c9.514667 0 12.970667 0.981333 16.426667 2.858667a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667v30.634666c0 9.514667-0.981333 12.970667-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816H554.666667v356.650666c0 9.514667-0.981333 12.970667-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816h-30.634666c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064c-1.834667-3.456-2.816-6.912-2.816-16.426667V554.666667H112.682667c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064C86.357333 540.288 85.333333 536.832 85.333333 527.36v-30.634667c0-9.514667 0.981333-12.970667 2.858667-16.426666a19.370667 19.370667 0 0 1 8.064-8.106667c3.456-1.834667 6.912-2.816 16.426667-2.816H469.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-add-circle-outlined': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M469.333333 469.333333V320a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V469.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H554.666667v149.333333a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V554.666667H320a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H469.333333z m42.666667 426.666667a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-refresh': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 1024 1024', version: '1.1', xmlns: 'http://www.w3.org/2000/svg', }, [ h('path', { d: 'M757.12 341.333333a298.666667 298.666667 0 1 0 41.173333 256h88.192A384 384 0 1 1 810.666667 270.634667V149.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333V384a42.666667 42.666667 0 0 1-42.666667 42.666667h-234.666666a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334h138.453333z', fill: 'currentColor', }), ], ), ]) }, }, 'app-unlink': { iconReader: () => { return h('i', [ h( 'svg', { style: { height: '100%', width: '100%' }, viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', }, [ h('g', { 'clip-path': 'url(#clip0_10754_9765)' }, [ h('path', { d: 'M1.23567 0.764126L0.76429 1.23549C0.634122 1.36565 0.634122 1.57668 0.76429 1.70685L13.9629 14.905C14.0931 15.0351 14.3042 15.0351 14.4343 14.905L14.9057 14.4336C15.0359 14.3034 15.0359 14.0924 14.9057 13.9622L1.70705 0.764126C1.57688 0.633963 1.36584 0.633963 1.23567 0.764126Z', fill: 'currentColor', }), h('path', { d: 'M9.77756 6.94871V3.33311C9.77756 3.22403 9.69895 3.1333 9.59528 3.11448L9.55534 3.1109H5.93959L4.60626 1.77762H9.55534C10.3858 1.77762 11.0643 2.42839 11.1086 3.24777L11.1109 3.33311V8.28199L9.77756 6.94871Z', fill: 'currentColor', }), h('path', { d: 'M0.888669 3.71681V8.66623L0.890971 8.75157C0.93528 9.57095 1.61375 10.2217 2.44422 10.2217H4.17756C4.32483 10.2217 4.44422 10.1023 4.44422 9.95506V9.15509C4.44422 9.00782 4.32483 8.88844 4.17756 8.88844H2.44422L2.40428 8.88486C2.30061 8.86604 2.222 8.77531 2.222 8.66623V5.05009L0.888669 3.71681Z', fill: 'currentColor', }), h('path', { d: 'M5.33311 8.16107V12.6661L5.33542 12.7514C5.37972 13.5708 6.0582 14.2216 6.88867 14.2216H11.3938L10.0605 12.8883H6.88867L6.84872 12.8847C6.74506 12.8659 6.66645 12.7751 6.66645 12.6661V9.49435L5.33311 8.16107Z', fill: 'currentColor', }), h('path', { d: 'M8.60626 5.77746L8.88867 6.05986V6.04411C8.88867 5.89684 8.76928 5.77746 8.622 5.77746H8.60626Z', fill: 'currentColor', }), h('path', { d: 'M15.5542 12.7251L14.222 11.393V7.33295C14.222 7.22386 14.1434 7.13313 14.0397 7.11431L13.9998 7.11073H12.2664C12.1192 7.11073 11.9998 6.99135 11.9998 6.84408V6.04411C11.9998 5.89684 12.1192 5.77746 12.2664 5.77746H13.9998C14.8303 5.77746 15.5087 6.42822 15.553 7.2476L15.5553 7.33295V12.6661C15.5553 12.6858 15.555 12.7055 15.5542 12.7251Z', fill: 'currentColor', }), ]), h('defs', [ h('clipPath', { id: 'clip0_10754_9765' }, [ h('rect', { width: '16', height: '15.9993', fill: 'currentColor' }), ]), ]), ], ), ]) }, }, // 动态加载的图标 ...dynamicIcons, } ================================================ FILE: ui/src/components/app-table/index.vue ================================================ ================================================ FILE: ui/src/components/app-table-infinite-scroll/index.vue ================================================ ================================================ FILE: ui/src/components/auto-tooltip/index.vue ================================================ ================================================ FILE: ui/src/components/back-button/index.vue ================================================ ================================================ FILE: ui/src/components/card-box/index.vue ================================================ ================================================ FILE: ui/src/components/card-checkbox/index.vue ================================================ ================================================ FILE: ui/src/components/codemirror-editor/index.vue ================================================ ================================================ FILE: ui/src/components/common-list/index.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/Demo.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/DemoConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/FormItem.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/FormItemLabel.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/data.ts ================================================ import { t } from '@/locales' const input_type_list = [ { label: t('dynamicsForm.input_type_list.TextInput'), value: 'TextInput', }, { label: t('dynamicsForm.input_type_list.TextareaInput'), value: 'TextareaInput', }, { label: t('dynamicsForm.input_type_list.JsonInput'), value: 'JsonInput', }, { label: t('dynamicsForm.input_type_list.PasswordInput'), value: 'PasswordInput', }, { label: t('dynamicsForm.input_type_list.SingleSelect'), value: 'SingleSelect', }, { label: t('dynamicsForm.input_type_list.MultiSelect'), value: 'MultiSelect', }, { label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCard', }, { label: t('dynamicsForm.input_type_list.RadioRow'), value: 'RadioRow', }, { label: t('dynamicsForm.input_type_list.MultiRow'), value: 'MultiRow', }, { label: t('dynamicsForm.input_type_list.Slider'), value: 'Slider', }, { label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInput', }, { label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePicker', }, { label: t('dynamicsForm.input_type_list.UploadInput'), value: 'UploadInput', }, { label: t('dynamicsForm.input_type_list.Model'), value: 'Model', }, ] export { input_type_list } ================================================ FILE: ui/src/components/dynamics-form/constructor/index.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/DatePickerConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/JsonInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/ModelConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/MultiRowConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/PasswordInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/RadioCardConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/RadioRowConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/SliderConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/SwitchInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/TextInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/TextareaInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/constructor/items/UploadInputConstructor.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/index.ts ================================================ import type { App } from 'vue' import type { Dict } from '@/api/type/common' import DynamicsForm from '@/components/dynamics-form/index.vue' let components: Dict = import.meta.glob('@/components/dynamics-form/**/**.vue', { eager: true, }) components = { ...components, ...import.meta.glob('@/components/dynamics-form/**/**/**.vue', { eager: true, }), } const install = (app: App) => { Object.keys(components).forEach((key: string) => { const commentName: string = key .substring(key.lastIndexOf('/') + 1, key.length) .replace('.vue', '') if (key !== '/src/components/dynamics-form/constructor/index.vue') { app.component(commentName, components[key].default) } }) app.component('DynamicsForm', DynamicsForm) } export default { install } ================================================ FILE: ui/src/components/dynamics-form/index.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/DatePicker.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/JsonInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/MultiRow.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/PasswordInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/TextInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/TextareaInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/complex/ArrayObjectCard.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/complex/ObjectCard.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/complex/TabCard.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/label/SettingLabel.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/label/TooltipLabel.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/layout/RowLayout.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/model/Model.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/model/provider-data.ts ================================================ export const providerList = [ { "provider": "model_azure_provider", "name": "Azure OpenAI", "icon": "" }, { "provider": "model_wenxin_provider", "name": "千帆大模型", "icon": "\n\n\n\n" }, { "provider": "model_ollama_provider", "name": "Ollama", "icon": " \n\n" }, { "provider": "model_openai_provider", "name": "OpenAI", "icon": "" }, { "provider": "model_docker_ai_provider", "name": "Docker AI", "icon": "\n\n\n" }, { "provider": "model_kimi_provider", "name": "Kimi", "icon": "" }, { "provider": "model_zhipu_provider", "name": "智谱 AI", "icon": "" }, { "provider": "model_xf_provider", "name": "讯飞星火", "icon": "" }, { "provider": "model_deepseek_provider", "name": "DeepSeek", "icon": "\n\t\n" }, { "provider": "model_gemini_provider", "name": "Gemini", "icon": "" }, { "provider": "model_volcanic_engine_provider", "name": "火山引擎", "icon": "\n\n \n\n" }, { "provider": "model_tencent_provider", "name": "腾讯混元", "icon": "\n\n \n\n" }, { "provider": "model_tencent_cloud_provider", "name": "腾讯云", "icon": "\n\n\n
\n
\n
\n" }, { "provider": "model_aws_bedrock_provider", "name": "Amazon Bedrock", "icon": "" }, { "provider": "model_local_provider", "name": "本地模型", "icon": "\n\n\n" }, { "provider": "model_xinference_provider", "name": "Xorbits Inference", "icon": "\n\n \n\n" }, { "provider": "model_vllm_provider", "name": "vLLM", "icon": "\n\n \n\n" }, { "provider": "aliyun_bai_lian_model_provider", "name": "阿里云百炼", "icon": "【icon】阿里百炼大模型" }, { "provider": "model_anthropic_provider", "name": "Anthropic", "icon": "" }, { "provider": "model_siliconCloud_provider", "name": "SILICONFLOW", "icon": "\n\n\n\n\n" }, { "provider": "model_regolo_provider", "name": "Regolo", "icon": "\n\n \n \n \n .cls-1 {\n fill: #303030;\n }\n\n .cls-2 {\n fill: #59e389;\n }\n \n \n \n \n \n \n \n \n \n\n" } ] ================================================ FILE: ui/src/components/dynamics-form/items/radio/Radio.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/radio/RadioButton.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/radio/RadioCard.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/radio/RadioRow.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/select/MultiSelect.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/select/SingleSelect.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/slider/Slider.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/switch/SwitchInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/table/ProgressTableItem.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/table/TableCheckbox.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/table/TableColumn.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/table/TableRadio.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/tree/Tree.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/items/upload/UploadInput.vue ================================================ ================================================ FILE: ui/src/components/dynamics-form/type.ts ================================================ import type { Dict } from '@/api/type/common' interface ViewCardItem { /** * 类型 */ type: 'eval' | 'default' /** * 标题 */ title: string /** * 值 根据类型不一样 取值也不一样 default= row[value_field] eval `${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%   ` */ value_field: string } interface TableColumn { /** * 字段|组件名称|可计算的模板字符串 */ property: string /** *表头 */ label: string /** * 表数据字段 */ value_field?: string attrs?: Attrs /** * 类型 */ type: 'eval' | 'component' | 'default' props_info?: PropsInfo } interface ColorItem { /** * 颜色#f56c6c */ color: string /** * 进度 */ percentage: number } interface Attrs { /** * 提示语 */ placeholder?: string /** * 标签的长度,例如 '50px'。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。 */ labelWidth?: string /** * 表单域标签的后缀 */ labelSuffix?: string /** * 星号的位置。 */ requireAsteriskPosition?: 'left' | 'right' color?: Array [propName: string]: any } interface PropsInfo { /** * 表格选择的card */ view_card?: Array /** * 表格选择 */ table_columns?: Array /** * 选中 message */ active_msg?: string /** * 组件样式 */ style?: Dict /** * el-form-item 样式 */ item_style?: Dict /** * 表单校验 这个和element校验一样 */ rules?: Dict /** * 默认 不为空校验提示 */ err_msg?: string /** *tabs的时候使用 */ tabs_label?: string [propName: string]: any } interface FormField { field: string /** * 输入框类型 */ input_type: string /** * 提示 */ label?: string | any /** * 是否 必填 */ required?: boolean /** * 默认值 */ default_value?: any /** * 是否显示默认值 */ show_default_value?: boolean /** * {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示 */ relation_show_field_dict?: Dict> /** * {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据 */ relation_trigger_field_dict?: Dict /** * 执行器类型 OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单 */ trigger_type?: 'OPTION_LIST' | 'CHILD_FORMS' /** * 前端attr数据 */ attrs?: Attrs /** * 其他额外信息 */ props_info?: PropsInfo /** * 下拉选字段field */ text_field?: string /** * 下拉选 value */ value_field?: string /** * 下拉选数据 */ option_list?: Array /** * 供应商 */ provider?: string /** * 执行函数 */ method?: string children?: Array required_asterisk?: boolean [propName: string]: any } export type { FormField } ================================================ FILE: ui/src/components/execution-detail-card/index.vue ================================================ ================================================ FILE: ui/src/components/folder-breadcrumb/index.vue ================================================ ================================================ FILE: ui/src/components/folder-tree/CreateFolderDialog.vue ================================================ ================================================ FILE: ui/src/components/folder-tree/MoveToDialog.vue ================================================ ================================================ FILE: ui/src/components/folder-tree/constant.ts ================================================ import { t } from '@/locales' export const SORT_TYPES = { CREATE_TIME_ASC: 'createTime-asc', CREATE_TIME_DESC: 'createTime-desc', NAME_ASC: 'name-asc', NAME_DESC: 'name-desc', CUSTOM: 'custom', } as const export type SortType = (typeof SORT_TYPES)[keyof typeof SORT_TYPES] export const SORT_MENU_CONFIG = [ { title: 'time', items: [ { label: t('components.folder.ascTime'), value: SORT_TYPES.CREATE_TIME_ASC }, { label: t('components.folder.descTime'), value: SORT_TYPES.CREATE_TIME_DESC }, ], }, { title: 'name', items: [ { label: t('components.folder.ascName'), value: SORT_TYPES.NAME_ASC }, { label: t('components.folder.descName'), value: SORT_TYPES.NAME_DESC }, ], }, { items: [{ label: t('components.folder.custom'), value: SORT_TYPES.CUSTOM }], }, ] ================================================ FILE: ui/src/components/folder-tree/index.vue ================================================ ================================================ FILE: ui/src/components/generate-related-dialog/index.vue ================================================ ================================================ FILE: ui/src/components/index.ts ================================================ import { type App } from 'vue' import LogoFull from './logo/LogoFull.vue' import LogoIcon from './logo/LogoIcon.vue' import SendIcon from './logo/SendIcon.vue' import dynamicsForm from './dynamics-form' import AppIcon from './app-icon/AppIcon.vue' import LayoutContainer from './layout-container/index.vue' import ContentContainer from './layout-container/ContentContainer.vue' import CardBox from './card-box/index.vue' import FolderTree from './folder-tree/index.vue' import CommonList from './common-list/index.vue' import BackButton from './back-button/index.vue' import AppTable from './app-table/index.vue' import CodemirrorEditor from './codemirror-editor/index.vue' import InfiniteScroll from './infinite-scroll/index.vue' import ModelSelect from './model-select/index.vue' import ReadWrite from './read-write/index.vue' import AutoTooltip from './auto-tooltip/index.vue' import MdEditor from './markdown/MdEditor.vue' import MdPreview from './markdown/MdPreview.vue' import MdEditorMagnify from './markdown/MdEditorMagnify.vue' import TagEllipsis from './tag-ellipsis/index.vue' import CardCheckbox from './card-checkbox/index.vue' import AiChat from './ai-chat/index.vue' import KnowledgeIcon from './app-icon/KnowledgeIcon.vue' import ToolIcon from './app-icon/ToolIcon.vue' import TriggerIcon from './app-icon/TriggerIcon.vue' import TagGroup from './tag-group/index.vue' import WorkspaceDropdown from './workspace-dropdown/index.vue' import FolderBreadcrumb from './folder-breadcrumb/index.vue' export default { install(app: App) { app.component('LogoFull', LogoFull) app.component('LogoIcon', LogoIcon) app.component('SendIcon', SendIcon) app.use(dynamicsForm) app.component('AppIcon', AppIcon) app.component('LayoutContainer', LayoutContainer) app.component('ContentContainer', ContentContainer) app.component('CardBox', CardBox) app.component('FolderTree', FolderTree) app.component('CommonList', CommonList) app.component('BackButton', BackButton) app.component('AppTable', AppTable) app.component('CodemirrorEditor', CodemirrorEditor) app.component('InfiniteScroll', InfiniteScroll) app.component('ModelSelect', ModelSelect) app.component('ReadWrite', ReadWrite) app.component('AutoTooltip', AutoTooltip) app.component('MdPreview', MdPreview) app.component('MdEditor', MdEditor) app.component('MdEditorMagnify', MdEditorMagnify) app.component('TagEllipsis', TagEllipsis) app.component('CardCheckbox', CardCheckbox) app.component('AiChat', AiChat) app.component('KnowledgeIcon', KnowledgeIcon) app.component('ToolIcon', ToolIcon) app.component('TriggerIcon', TriggerIcon) app.component('TagGroup', TagGroup) app.component('WorkspaceDropdown', WorkspaceDropdown) app.component('FolderBreadcrumb', FolderBreadcrumb) }, } ================================================ FILE: ui/src/components/infinite-scroll/index.vue ================================================ ================================================ FILE: ui/src/components/layout-container/ContentContainer.vue ================================================ ================================================ FILE: ui/src/components/layout-container/index.vue ================================================ ================================================ FILE: ui/src/components/loading/DownloadLoading.vue ================================================ ================================================ FILE: ui/src/components/logo/LogoFull.vue ================================================ ================================================ FILE: ui/src/components/logo/LogoIcon.vue ================================================ ================================================ FILE: ui/src/components/logo/SendIcon.vue ================================================ ================================================ FILE: ui/src/components/markdown/EchartsRander.vue ================================================ ================================================ FILE: ui/src/components/markdown/FormRander.vue ================================================ ================================================ FILE: ui/src/components/markdown/HtmlRander.vue ================================================ ================================================ FILE: ui/src/components/markdown/IframeRender.vue ================================================