From 3e011109ad4d08d9cf686b1aeb32eb317842f14a Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 25 Oct 2024 11:26:41 +0800 Subject: [PATCH] merge main --- .../app/apps/workflow_logging_callback.py | 220 +++++ api/core/app/segments/__init__.py | 49 ++ api/core/app/segments/exc.py | 2 + api/core/app/segments/factory.py | 76 ++ api/core/app/segments/parser.py | 18 + api/core/app/segments/segment_group.py | 22 + api/core/app/segments/segments.py | 126 +++ api/core/app/segments/types.py | 15 + api/core/app/segments/variables.py | 75 ++ api/core/entities/message_entities.py | 29 + api/core/file/file_obj.py | 145 ++++ api/core/file/message_file_parser.py | 243 ++++++ api/core/file/upload_file_parser.py | 79 ++ .../builtin/feishu_base/_assets/icon.svg | 47 ++ .../feishu_base/tools/add_base_record.py | 56 ++ .../feishu_base/tools/add_base_record.yaml | 66 ++ .../feishu_base/tools/create_base_table.py | 48 ++ .../feishu_base/tools/create_base_table.yaml | 106 +++ .../feishu_base/tools/delete_base_records.py | 56 ++ .../tools/delete_base_records.yaml | 60 ++ .../feishu_base/tools/delete_base_tables.py | 46 ++ .../feishu_base/tools/delete_base_tables.yaml | 48 ++ .../tools/get_tenant_access_token.py | 48 ++ .../tools/get_tenant_access_token.yaml | 39 + .../feishu_base/tools/list_base_records.py | 65 ++ .../feishu_base/tools/list_base_records.yaml | 108 +++ .../feishu_base/tools/list_base_tables.py | 47 ++ .../feishu_base/tools/list_base_tables.yaml | 65 ++ .../feishu_base/tools/read_base_record.py | 49 ++ .../feishu_base/tools/read_base_record.yaml | 60 ++ .../feishu_base/tools/update_base_record.py | 60 ++ .../feishu_base/tools/update_base_record.yaml | 78 ++ .../tools/utils/tool_parameter_converter.py | 71 ++ .../entities/base_node_data_entities.py | 24 + api/core/workflow/nodes/base_node.py | 117 +++ api/core/workflow/nodes/event.py | 20 + .../nodes/http_request/http_executor.py | 343 ++++++++ .../nodes/http_request/http_request_node.py | 165 ++++ api/core/workflow/nodes/llm/llm_node.py | 774 ++++++++++++++++++ .../tools/test_tool_parameter_converter.py | 56 ++ .../select-type-item/style.module.css | 40 + .../config-vision/radio-group/index.tsx | 40 + .../radio-group/style.module.css | 24 + .../config-voice/param-config-content.tsx | 220 +++++ .../config-voice/param-config.tsx | 41 + .../config/feature/add-feature-btn/index.tsx | 40 + .../choose-feature/feature-item/index.tsx | 52 ++ .../feature-item/preview-imgs/citation.png | Bin 0 -> 29852 bytes .../feature-item/preview-imgs/citation.svg | 150 ++++ .../citations-and-attributions-preview@2x.png | Bin 0 -> 20827 bytes .../conversation-opener-preview@2x.png | Bin 0 -> 14409 bytes .../more-like-this-preview@2x.png | Bin 0 -> 19839 bytes .../preview-imgs/more-like-this.png | Bin 0 -> 30202 bytes .../preview-imgs/more-like-this.svg | 188 +++++ .../next-question-suggestion-preview@2x.png | Bin 0 -> 28325 bytes .../preview-imgs/opening-statement.png | Bin 0 -> 19955 bytes .../opening-suggestion-preview@2x.png | Bin 0 -> 24140 bytes .../speech-to-text-preview@2x.png | Bin 0 -> 16929 bytes .../preview-imgs/speech-to-text.png | Bin 0 -> 24529 bytes .../preview-imgs/speech-to-text.svg | 100 +++ .../suggested-questions-after-answer.png | Bin 0 -> 42447 bytes .../suggested-questions-after-answer.svg | 163 ++++ .../text-to-audio-preview-assistant@2x.png | Bin 0 -> 23500 bytes .../text-to-audio-preview-completion@2x.png | Bin 0 -> 18462 bytes .../feature-item/style.module.css | 41 + .../config/feature/choose-feature/index.tsx | 172 ++++ .../config/feature/feature-group/index.tsx | 31 + .../features/chat-group/citation/index.tsx | 25 + .../features/chat-group/index.tsx | 65 ++ .../chat-group/speech-to-text/index.tsx | 25 + .../index.tsx | 34 + .../chat-group/text-to-speech/index.tsx | 55 ++ .../annotation/annotation-ctrl-btn/index.tsx | 135 +++ .../toolbox/annotation/config-param-modal.tsx | 139 ++++ .../configuration/toolbox/annotation/type.ts | 4 + .../annotation/use-annotation-config.ts | 89 ++ .../toolbox/moderation/form-generation.tsx | 79 ++ .../toolbox/moderation/index.tsx | 80 ++ .../toolbox/moderation/moderation-content.tsx | 72 ++ .../moderation/moderation-setting-modal.tsx | 373 +++++++++ .../score-slider/base-slider/index.tsx | 38 + .../score-slider/base-slider/style.module.css | 20 + .../toolbox/score-slider/index.tsx | 46 ++ .../components/base/chat/chat/chat-input.tsx | 258 ++++++ .../feature-choose/feature-group/index.tsx | 31 + .../feature-choose/feature-item/index.tsx | 96 +++ .../feature-item/preview-imgs/citation.svg | 150 ++++ .../citations-and-attributions-preview@2x.png | Bin 0 -> 20827 bytes .../conversation-opener-preview@2x.png | Bin 0 -> 14409 bytes .../more-like-this-preview@2x.png | Bin 0 -> 19839 bytes .../preview-imgs/more-like-this.svg | 188 +++++ .../next-question-suggestion-preview@2x.png | Bin 0 -> 28325 bytes .../preview-imgs/opening-statement.png | Bin 0 -> 19955 bytes .../opening-suggestion-preview@2x.png | Bin 0 -> 24140 bytes .../speech-to-text-preview@2x.png | Bin 0 -> 16929 bytes .../preview-imgs/speech-to-text.svg | 100 +++ .../suggested-questions-after-answer.svg | 163 ++++ .../text-to-audio-preview-assistant@2x.png | Bin 0 -> 23500 bytes .../text-to-audio-preview-completion@2x.png | Bin 0 -> 18462 bytes .../feature-item/style.module.css | 41 + .../features/feature-choose/feature-modal.tsx | 147 ++++ .../base/features/feature-choose/index.tsx | 42 + .../features/feature-panel/citation/index.tsx | 25 + .../feature-panel/file-upload/index.tsx | 63 ++ .../file-upload/param-config-content.tsx | 119 +++ .../file-upload/param-config.tsx | 49 ++ .../file-upload/radio-group/index.tsx | 40 + .../file-upload/radio-group/style.module.css | 24 + .../base/features/feature-panel/index.tsx | 119 +++ .../moderation/form-generation.tsx | 80 ++ .../feature-panel/moderation/index.tsx | 108 +++ .../moderation/moderation-content.tsx | 73 ++ .../moderation/moderation-setting-modal.tsx | 376 +++++++++ .../feature-panel/opening-statement/index.tsx | 327 ++++++++ .../score-slider/base-slider/index.tsx | 38 + .../score-slider/base-slider/style.module.css | 20 + .../feature-panel/score-slider/index.tsx | 46 ++ .../feature-panel/speech-to-text/index.tsx | 22 + .../index.tsx | 25 + .../feature-panel/text-to-speech/index.tsx | 62 ++ .../text-to-speech/param-config-content.tsx | 241 ++++++ .../text-to-speech/params-config.tsx | 48 ++ web/app/signin/forms.tsx | 34 + web/app/signin/userSSOForm.tsx | 107 +++ 124 files changed, 9664 insertions(+) create mode 100644 api/core/app/apps/workflow_logging_callback.py create mode 100644 api/core/app/segments/__init__.py create mode 100644 api/core/app/segments/exc.py create mode 100644 api/core/app/segments/factory.py create mode 100644 api/core/app/segments/parser.py create mode 100644 api/core/app/segments/segment_group.py create mode 100644 api/core/app/segments/segments.py create mode 100644 api/core/app/segments/types.py create mode 100644 api/core/app/segments/variables.py create mode 100644 api/core/entities/message_entities.py create mode 100644 api/core/file/file_obj.py create mode 100644 api/core/file/message_file_parser.py create mode 100644 api/core/file/upload_file_parser.py create mode 100644 api/core/tools/provider/builtin/feishu_base/_assets/icon.svg create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/add_base_record.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/add_base_record.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/create_base_table.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/create_base_table.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/list_base_records.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/list_base_records.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/read_base_record.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/read_base_record.yaml create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/update_base_record.py create mode 100644 api/core/tools/provider/builtin/feishu_base/tools/update_base_record.yaml create mode 100644 api/core/tools/utils/tool_parameter_converter.py create mode 100644 api/core/workflow/entities/base_node_data_entities.py create mode 100644 api/core/workflow/nodes/base_node.py create mode 100644 api/core/workflow/nodes/event.py create mode 100644 api/core/workflow/nodes/http_request/http_executor.py create mode 100644 api/core/workflow/nodes/http_request/http_request_node.py create mode 100644 api/core/workflow/nodes/llm/llm_node.py create mode 100644 api/tests/unit_tests/core/tools/test_tool_parameter_converter.py create mode 100644 web/app/components/app/configuration/config-var/select-type-item/style.module.css create mode 100644 web/app/components/app/configuration/config-vision/radio-group/index.tsx create mode 100644 web/app/components/app/configuration/config-vision/radio-group/style.module.css create mode 100644 web/app/components/app/configuration/config-voice/param-config-content.tsx create mode 100644 web/app/components/app/configuration/config-voice/param-config.tsx create mode 100644 web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.svg create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/conversation-opener-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this.svg create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/next-question-suggestion-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/opening-statement.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/opening-suggestion-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.svg create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/suggested-questions-after-answer.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/suggested-questions-after-answer.svg create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-completion@2x.png create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css create mode 100644 web/app/components/app/configuration/config/feature/choose-feature/index.tsx create mode 100644 web/app/components/app/configuration/config/feature/feature-group/index.tsx create mode 100644 web/app/components/app/configuration/features/chat-group/citation/index.tsx create mode 100644 web/app/components/app/configuration/features/chat-group/index.tsx create mode 100644 web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx create mode 100644 web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx create mode 100644 web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx create mode 100644 web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx create mode 100644 web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx create mode 100644 web/app/components/app/configuration/toolbox/annotation/type.ts create mode 100644 web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts create mode 100644 web/app/components/app/configuration/toolbox/moderation/form-generation.tsx create mode 100644 web/app/components/app/configuration/toolbox/moderation/index.tsx create mode 100644 web/app/components/app/configuration/toolbox/moderation/moderation-content.tsx create mode 100644 web/app/components/app/configuration/toolbox/moderation/moderation-setting-modal.tsx create mode 100644 web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx create mode 100644 web/app/components/app/configuration/toolbox/score-slider/base-slider/style.module.css create mode 100644 web/app/components/app/configuration/toolbox/score-slider/index.tsx create mode 100644 web/app/components/base/chat/chat/chat-input.tsx create mode 100644 web/app/components/base/features/feature-choose/feature-group/index.tsx create mode 100644 web/app/components/base/features/feature-choose/feature-item/index.tsx create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/citation.svg create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/citations-and-attributions-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/conversation-opener-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/more-like-this-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/more-like-this.svg create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/next-question-suggestion-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/opening-statement.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/opening-suggestion-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/speech-to-text-preview@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/speech-to-text.svg create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/suggested-questions-after-answer.svg create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/preview-imgs/text-to-audio-preview-completion@2x.png create mode 100644 web/app/components/base/features/feature-choose/feature-item/style.module.css create mode 100644 web/app/components/base/features/feature-choose/feature-modal.tsx create mode 100644 web/app/components/base/features/feature-choose/index.tsx create mode 100644 web/app/components/base/features/feature-panel/citation/index.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/index.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config-content.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/param-config.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx create mode 100644 web/app/components/base/features/feature-panel/file-upload/radio-group/style.module.css create mode 100644 web/app/components/base/features/feature-panel/index.tsx create mode 100644 web/app/components/base/features/feature-panel/moderation/form-generation.tsx create mode 100644 web/app/components/base/features/feature-panel/moderation/index.tsx create mode 100644 web/app/components/base/features/feature-panel/moderation/moderation-content.tsx create mode 100644 web/app/components/base/features/feature-panel/moderation/moderation-setting-modal.tsx create mode 100644 web/app/components/base/features/feature-panel/opening-statement/index.tsx create mode 100644 web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx create mode 100644 web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css create mode 100644 web/app/components/base/features/feature-panel/score-slider/index.tsx create mode 100644 web/app/components/base/features/feature-panel/speech-to-text/index.tsx create mode 100644 web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx create mode 100644 web/app/components/base/features/feature-panel/text-to-speech/index.tsx create mode 100644 web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx create mode 100644 web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx create mode 100644 web/app/signin/forms.tsx create mode 100644 web/app/signin/userSSOForm.tsx diff --git a/api/core/app/apps/workflow_logging_callback.py b/api/core/app/apps/workflow_logging_callback.py new file mode 100644 index 0000000000..60683b0f21 --- /dev/null +++ b/api/core/app/apps/workflow_logging_callback.py @@ -0,0 +1,220 @@ +from typing import Optional + +from core.model_runtime.utils.encoders import jsonable_encoder +from core.workflow.callbacks.base_workflow_callback import WorkflowCallback +from core.workflow.graph_engine.entities.event import ( + GraphEngineEvent, + GraphRunFailedEvent, + GraphRunStartedEvent, + GraphRunSucceededEvent, + IterationRunFailedEvent, + IterationRunNextEvent, + IterationRunStartedEvent, + IterationRunSucceededEvent, + NodeRunFailedEvent, + NodeRunStartedEvent, + NodeRunStreamChunkEvent, + NodeRunSucceededEvent, + ParallelBranchRunFailedEvent, + ParallelBranchRunStartedEvent, + ParallelBranchRunSucceededEvent, +) + +_TEXT_COLOR_MAPPING = { + "blue": "36;1", + "yellow": "33;1", + "pink": "38;5;200", + "green": "32;1", + "red": "31;1", +} + + +class WorkflowLoggingCallback(WorkflowCallback): + def __init__(self) -> None: + self.current_node_id = None + + def on_event(self, event: GraphEngineEvent) -> None: + if isinstance(event, GraphRunStartedEvent): + self.print_text("\n[GraphRunStartedEvent]", color="pink") + elif isinstance(event, GraphRunSucceededEvent): + self.print_text("\n[GraphRunSucceededEvent]", color="green") + elif isinstance(event, GraphRunFailedEvent): + self.print_text(f"\n[GraphRunFailedEvent] reason: {event.error}", color="red") + elif isinstance(event, NodeRunStartedEvent): + self.on_workflow_node_execute_started(event=event) + elif isinstance(event, NodeRunSucceededEvent): + self.on_workflow_node_execute_succeeded(event=event) + elif isinstance(event, NodeRunFailedEvent): + self.on_workflow_node_execute_failed(event=event) + elif isinstance(event, NodeRunStreamChunkEvent): + self.on_node_text_chunk(event=event) + elif isinstance(event, ParallelBranchRunStartedEvent): + self.on_workflow_parallel_started(event=event) + elif isinstance(event, ParallelBranchRunSucceededEvent | ParallelBranchRunFailedEvent): + self.on_workflow_parallel_completed(event=event) + elif isinstance(event, IterationRunStartedEvent): + self.on_workflow_iteration_started(event=event) + elif isinstance(event, IterationRunNextEvent): + self.on_workflow_iteration_next(event=event) + elif isinstance(event, IterationRunSucceededEvent | IterationRunFailedEvent): + self.on_workflow_iteration_completed(event=event) + else: + self.print_text(f"\n[{event.__class__.__name__}]", color="blue") + + def on_workflow_node_execute_started(self, event: NodeRunStartedEvent) -> None: + """ + Workflow node execute started + """ + self.print_text("\n[NodeRunStartedEvent]", color="yellow") + self.print_text(f"Node ID: {event.node_id}", color="yellow") + self.print_text(f"Node Title: {event.node_data.title}", color="yellow") + self.print_text(f"Type: {event.node_type.value}", color="yellow") + + def on_workflow_node_execute_succeeded(self, event: NodeRunSucceededEvent) -> None: + """ + Workflow node execute succeeded + """ + route_node_state = event.route_node_state + + self.print_text("\n[NodeRunSucceededEvent]", color="green") + self.print_text(f"Node ID: {event.node_id}", color="green") + self.print_text(f"Node Title: {event.node_data.title}", color="green") + self.print_text(f"Type: {event.node_type.value}", color="green") + + if route_node_state.node_run_result: + node_run_result = route_node_state.node_run_result + self.print_text( + f"Inputs: {jsonable_encoder(node_run_result.inputs) if node_run_result.inputs else ''}", + color="green", + ) + self.print_text( + f"Process Data: " + f"{jsonable_encoder(node_run_result.process_data) if node_run_result.process_data else ''}", + color="green", + ) + self.print_text( + f"Outputs: {jsonable_encoder(node_run_result.outputs) if node_run_result.outputs else ''}", + color="green", + ) + self.print_text( + f"Metadata: {jsonable_encoder(node_run_result.metadata) if node_run_result.metadata else ''}", + color="green", + ) + + def on_workflow_node_execute_failed(self, event: NodeRunFailedEvent) -> None: + """ + Workflow node execute failed + """ + route_node_state = event.route_node_state + + self.print_text("\n[NodeRunFailedEvent]", color="red") + self.print_text(f"Node ID: {event.node_id}", color="red") + self.print_text(f"Node Title: {event.node_data.title}", color="red") + self.print_text(f"Type: {event.node_type.value}", color="red") + + if route_node_state.node_run_result: + node_run_result = route_node_state.node_run_result + self.print_text(f"Error: {node_run_result.error}", color="red") + self.print_text( + f"Inputs: {jsonable_encoder(node_run_result.inputs) if node_run_result.inputs else ''}", + color="red", + ) + self.print_text( + f"Process Data: " + f"{jsonable_encoder(node_run_result.process_data) if node_run_result.process_data else ''}", + color="red", + ) + self.print_text( + f"Outputs: {jsonable_encoder(node_run_result.outputs) if node_run_result.outputs else ''}", + color="red", + ) + + def on_node_text_chunk(self, event: NodeRunStreamChunkEvent) -> None: + """ + Publish text chunk + """ + route_node_state = event.route_node_state + if not self.current_node_id or self.current_node_id != route_node_state.node_id: + self.current_node_id = route_node_state.node_id + self.print_text("\n[NodeRunStreamChunkEvent]") + self.print_text(f"Node ID: {route_node_state.node_id}") + + node_run_result = route_node_state.node_run_result + if node_run_result: + self.print_text( + f"Metadata: {jsonable_encoder(node_run_result.metadata) if node_run_result.metadata else ''}" + ) + + self.print_text(event.chunk_content, color="pink", end="") + + def on_workflow_parallel_started(self, event: ParallelBranchRunStartedEvent) -> None: + """ + Publish parallel started + """ + self.print_text("\n[ParallelBranchRunStartedEvent]", color="blue") + self.print_text(f"Parallel ID: {event.parallel_id}", color="blue") + self.print_text(f"Branch ID: {event.parallel_start_node_id}", color="blue") + if event.in_iteration_id: + self.print_text(f"Iteration ID: {event.in_iteration_id}", color="blue") + + def on_workflow_parallel_completed( + self, event: ParallelBranchRunSucceededEvent | ParallelBranchRunFailedEvent + ) -> None: + """ + Publish parallel completed + """ + if isinstance(event, ParallelBranchRunSucceededEvent): + color = "blue" + elif isinstance(event, ParallelBranchRunFailedEvent): + color = "red" + + self.print_text( + "\n[ParallelBranchRunSucceededEvent]" + if isinstance(event, ParallelBranchRunSucceededEvent) + else "\n[ParallelBranchRunFailedEvent]", + color=color, + ) + self.print_text(f"Parallel ID: {event.parallel_id}", color=color) + self.print_text(f"Branch ID: {event.parallel_start_node_id}", color=color) + if event.in_iteration_id: + self.print_text(f"Iteration ID: {event.in_iteration_id}", color=color) + + if isinstance(event, ParallelBranchRunFailedEvent): + self.print_text(f"Error: {event.error}", color=color) + + def on_workflow_iteration_started(self, event: IterationRunStartedEvent) -> None: + """ + Publish iteration started + """ + self.print_text("\n[IterationRunStartedEvent]", color="blue") + self.print_text(f"Iteration Node ID: {event.iteration_id}", color="blue") + + def on_workflow_iteration_next(self, event: IterationRunNextEvent) -> None: + """ + Publish iteration next + """ + self.print_text("\n[IterationRunNextEvent]", color="blue") + self.print_text(f"Iteration Node ID: {event.iteration_id}", color="blue") + self.print_text(f"Iteration Index: {event.index}", color="blue") + + def on_workflow_iteration_completed(self, event: IterationRunSucceededEvent | IterationRunFailedEvent) -> None: + """ + Publish iteration completed + """ + self.print_text( + "\n[IterationRunSucceededEvent]" + if isinstance(event, IterationRunSucceededEvent) + else "\n[IterationRunFailedEvent]", + color="blue", + ) + self.print_text(f"Node ID: {event.iteration_id}", color="blue") + + def print_text(self, text: str, color: Optional[str] = None, end: str = "\n") -> None: + """Print text with highlighting and no end characters.""" + text_to_print = self._get_colored_text(text, color) if color else text + print(f"{text_to_print}", end=end) + + def _get_colored_text(self, text: str, color: str) -> str: + """Get colored text.""" + color_str = _TEXT_COLOR_MAPPING[color] + return f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m" diff --git a/api/core/app/segments/__init__.py b/api/core/app/segments/__init__.py new file mode 100644 index 0000000000..652ef243b4 --- /dev/null +++ b/api/core/app/segments/__init__.py @@ -0,0 +1,49 @@ +from .segment_group import SegmentGroup +from .segments import ( + ArrayAnySegment, + ArraySegment, + FloatSegment, + IntegerSegment, + NoneSegment, + ObjectSegment, + Segment, + StringSegment, +) +from .types import SegmentType +from .variables import ( + ArrayAnyVariable, + ArrayNumberVariable, + ArrayObjectVariable, + ArrayStringVariable, + FloatVariable, + IntegerVariable, + NoneVariable, + ObjectVariable, + SecretVariable, + StringVariable, + Variable, +) + +__all__ = [ + "IntegerVariable", + "FloatVariable", + "ObjectVariable", + "SecretVariable", + "StringVariable", + "ArrayAnyVariable", + "Variable", + "SegmentType", + "SegmentGroup", + "Segment", + "NoneSegment", + "NoneVariable", + "IntegerSegment", + "FloatSegment", + "ObjectSegment", + "ArrayAnySegment", + "StringSegment", + "ArrayStringVariable", + "ArrayNumberVariable", + "ArrayObjectVariable", + "ArraySegment", +] diff --git a/api/core/app/segments/exc.py b/api/core/app/segments/exc.py new file mode 100644 index 0000000000..5cf67c3bac --- /dev/null +++ b/api/core/app/segments/exc.py @@ -0,0 +1,2 @@ +class VariableError(ValueError): + pass diff --git a/api/core/app/segments/factory.py b/api/core/app/segments/factory.py new file mode 100644 index 0000000000..40a69ed4eb --- /dev/null +++ b/api/core/app/segments/factory.py @@ -0,0 +1,76 @@ +from collections.abc import Mapping +from typing import Any + +from configs import dify_config + +from .exc import VariableError +from .segments import ( + ArrayAnySegment, + FloatSegment, + IntegerSegment, + NoneSegment, + ObjectSegment, + Segment, + StringSegment, +) +from .types import SegmentType +from .variables import ( + ArrayNumberVariable, + ArrayObjectVariable, + ArrayStringVariable, + FloatVariable, + IntegerVariable, + ObjectVariable, + SecretVariable, + StringVariable, + Variable, +) + + +def build_variable_from_mapping(mapping: Mapping[str, Any], /) -> Variable: + if (value_type := mapping.get("value_type")) is None: + raise VariableError("missing value type") + if not mapping.get("name"): + raise VariableError("missing name") + if (value := mapping.get("value")) is None: + raise VariableError("missing value") + match value_type: + case SegmentType.STRING: + result = StringVariable.model_validate(mapping) + case SegmentType.SECRET: + result = SecretVariable.model_validate(mapping) + case SegmentType.NUMBER if isinstance(value, int): + result = IntegerVariable.model_validate(mapping) + case SegmentType.NUMBER if isinstance(value, float): + result = FloatVariable.model_validate(mapping) + case SegmentType.NUMBER if not isinstance(value, float | int): + raise VariableError(f"invalid number value {value}") + case SegmentType.OBJECT if isinstance(value, dict): + result = ObjectVariable.model_validate(mapping) + case SegmentType.ARRAY_STRING if isinstance(value, list): + result = ArrayStringVariable.model_validate(mapping) + case SegmentType.ARRAY_NUMBER if isinstance(value, list): + result = ArrayNumberVariable.model_validate(mapping) + case SegmentType.ARRAY_OBJECT if isinstance(value, list): + result = ArrayObjectVariable.model_validate(mapping) + case _: + raise VariableError(f"not supported value type {value_type}") + if result.size > dify_config.MAX_VARIABLE_SIZE: + raise VariableError(f"variable size {result.size} exceeds limit {dify_config.MAX_VARIABLE_SIZE}") + return result + + +def build_segment(value: Any, /) -> Segment: + if value is None: + return NoneSegment() + if isinstance(value, str): + return StringSegment(value=value) + if isinstance(value, int): + return IntegerSegment(value=value) + if isinstance(value, float): + return FloatSegment(value=value) + if isinstance(value, dict): + return ObjectSegment(value=value) + if isinstance(value, list): + return ArrayAnySegment(value=value) + raise ValueError(f"not supported value {value}") diff --git a/api/core/app/segments/parser.py b/api/core/app/segments/parser.py new file mode 100644 index 0000000000..3c4d7046f4 --- /dev/null +++ b/api/core/app/segments/parser.py @@ -0,0 +1,18 @@ +import re + +from core.workflow.entities.variable_pool import VariablePool + +from . import SegmentGroup, factory + +VARIABLE_PATTERN = re.compile(r"\{\{#([a-zA-Z0-9_]{1,50}(?:\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10})#\}\}") + + +def convert_template(*, template: str, variable_pool: VariablePool): + parts = re.split(VARIABLE_PATTERN, template) + segments = [] + for part in filter(lambda x: x, parts): + if "." in part and (value := variable_pool.get(part.split("."))): + segments.append(value) + else: + segments.append(factory.build_segment(part)) + return SegmentGroup(value=segments) diff --git a/api/core/app/segments/segment_group.py b/api/core/app/segments/segment_group.py new file mode 100644 index 0000000000..b363255b2c --- /dev/null +++ b/api/core/app/segments/segment_group.py @@ -0,0 +1,22 @@ +from .segments import Segment +from .types import SegmentType + + +class SegmentGroup(Segment): + value_type: SegmentType = SegmentType.GROUP + value: list[Segment] + + @property + def text(self): + return "".join([segment.text for segment in self.value]) + + @property + def log(self): + return "".join([segment.log for segment in self.value]) + + @property + def markdown(self): + return "".join([segment.markdown for segment in self.value]) + + def to_object(self): + return [segment.to_object() for segment in self.value] diff --git a/api/core/app/segments/segments.py b/api/core/app/segments/segments.py new file mode 100644 index 0000000000..b26b3c8291 --- /dev/null +++ b/api/core/app/segments/segments.py @@ -0,0 +1,126 @@ +import json +import sys +from collections.abc import Mapping, Sequence +from typing import Any + +from pydantic import BaseModel, ConfigDict, field_validator + +from .types import SegmentType + + +class Segment(BaseModel): + model_config = ConfigDict(frozen=True) + + value_type: SegmentType + value: Any + + @field_validator("value_type") + @classmethod + def validate_value_type(cls, value): + """ + This validator checks if the provided value is equal to the default value of the 'value_type' field. + If the value is different, a ValueError is raised. + """ + if value != cls.model_fields["value_type"].default: + raise ValueError("Cannot modify 'value_type'") + return value + + @property + def text(self) -> str: + return str(self.value) + + @property + def log(self) -> str: + return str(self.value) + + @property + def markdown(self) -> str: + return str(self.value) + + @property + def size(self) -> int: + return sys.getsizeof(self.value) + + def to_object(self) -> Any: + return self.value + + +class NoneSegment(Segment): + value_type: SegmentType = SegmentType.NONE + value: None = None + + @property + def text(self) -> str: + return "null" + + @property + def log(self) -> str: + return "null" + + @property + def markdown(self) -> str: + return "null" + + +class StringSegment(Segment): + value_type: SegmentType = SegmentType.STRING + value: str + + +class FloatSegment(Segment): + value_type: SegmentType = SegmentType.NUMBER + value: float + + +class IntegerSegment(Segment): + value_type: SegmentType = SegmentType.NUMBER + value: int + + +class ObjectSegment(Segment): + value_type: SegmentType = SegmentType.OBJECT + value: Mapping[str, Any] + + @property + def text(self) -> str: + return json.dumps(self.model_dump()["value"], ensure_ascii=False) + + @property + def log(self) -> str: + return json.dumps(self.model_dump()["value"], ensure_ascii=False, indent=2) + + @property + def markdown(self) -> str: + return json.dumps(self.model_dump()["value"], ensure_ascii=False, indent=2) + + +class ArraySegment(Segment): + @property + def markdown(self) -> str: + items = [] + for item in self.value: + if hasattr(item, "to_markdown"): + items.append(item.to_markdown()) + else: + items.append(str(item)) + return "\n".join(items) + + +class ArrayAnySegment(ArraySegment): + value_type: SegmentType = SegmentType.ARRAY_ANY + value: Sequence[Any] + + +class ArrayStringSegment(ArraySegment): + value_type: SegmentType = SegmentType.ARRAY_STRING + value: Sequence[str] + + +class ArrayNumberSegment(ArraySegment): + value_type: SegmentType = SegmentType.ARRAY_NUMBER + value: Sequence[float | int] + + +class ArrayObjectSegment(ArraySegment): + value_type: SegmentType = SegmentType.ARRAY_OBJECT + value: Sequence[Mapping[str, Any]] diff --git a/api/core/app/segments/types.py b/api/core/app/segments/types.py new file mode 100644 index 0000000000..9cf0856df5 --- /dev/null +++ b/api/core/app/segments/types.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class SegmentType(str, Enum): + NONE = "none" + NUMBER = "number" + STRING = "string" + SECRET = "secret" + ARRAY_ANY = "array[any]" + ARRAY_STRING = "array[string]" + ARRAY_NUMBER = "array[number]" + ARRAY_OBJECT = "array[object]" + OBJECT = "object" + + GROUP = "group" diff --git a/api/core/app/segments/variables.py b/api/core/app/segments/variables.py new file mode 100644 index 0000000000..f0e403ab8d --- /dev/null +++ b/api/core/app/segments/variables.py @@ -0,0 +1,75 @@ +from pydantic import Field + +from core.helper import encrypter + +from .segments import ( + ArrayAnySegment, + ArrayNumberSegment, + ArrayObjectSegment, + ArrayStringSegment, + FloatSegment, + IntegerSegment, + NoneSegment, + ObjectSegment, + Segment, + StringSegment, +) +from .types import SegmentType + + +class Variable(Segment): + """ + A variable is a segment that has a name. + """ + + id: str = Field( + default="", + description="Unique identity for variable. It's only used by environment variables now.", + ) + name: str + description: str = Field(default="", description="Description of the variable.") + + +class StringVariable(StringSegment, Variable): + pass + + +class FloatVariable(FloatSegment, Variable): + pass + + +class IntegerVariable(IntegerSegment, Variable): + pass + + +class ObjectVariable(ObjectSegment, Variable): + pass + + +class ArrayAnyVariable(ArrayAnySegment, Variable): + pass + + +class ArrayStringVariable(ArrayStringSegment, Variable): + pass + + +class ArrayNumberVariable(ArrayNumberSegment, Variable): + pass + + +class ArrayObjectVariable(ArrayObjectSegment, Variable): + pass + + +class SecretVariable(StringVariable): + value_type: SegmentType = SegmentType.SECRET + + @property + def log(self) -> str: + return encrypter.obfuscated_token(self.value) + + +class NoneVariable(NoneSegment, Variable): + value_type: SegmentType = SegmentType.NONE + value: None = None diff --git a/api/core/entities/message_entities.py b/api/core/entities/message_entities.py new file mode 100644 index 0000000000..10bc9f6ed7 --- /dev/null +++ b/api/core/entities/message_entities.py @@ -0,0 +1,29 @@ +import enum +from typing import Any + +from pydantic import BaseModel + + +class PromptMessageFileType(enum.Enum): + IMAGE = "image" + + @staticmethod + def value_of(value): + for member in PromptMessageFileType: + if member.value == value: + return member + raise ValueError(f"No matching enum found for value '{value}'") + + +class PromptMessageFile(BaseModel): + type: PromptMessageFileType + data: Any = None + + +class ImagePromptMessageFile(PromptMessageFile): + class DETAIL(enum.Enum): + LOW = "low" + HIGH = "high" + + type: PromptMessageFileType = PromptMessageFileType.IMAGE + detail: DETAIL = DETAIL.LOW diff --git a/api/core/file/file_obj.py b/api/core/file/file_obj.py new file mode 100644 index 0000000000..5c4e694025 --- /dev/null +++ b/api/core/file/file_obj.py @@ -0,0 +1,145 @@ +import enum +from typing import Any, Optional + +from pydantic import BaseModel + +from core.file.tool_file_parser import ToolFileParser +from core.file.upload_file_parser import UploadFileParser +from core.model_runtime.entities.message_entities import ImagePromptMessageContent +from extensions.ext_database import db + + +class FileExtraConfig(BaseModel): + """ + File Upload Entity. + """ + + image_config: Optional[dict[str, Any]] = None + + +class FileType(enum.Enum): + IMAGE = "image" + + @staticmethod + def value_of(value): + for member in FileType: + if member.value == value: + return member + raise ValueError(f"No matching enum found for value '{value}'") + + +class FileTransferMethod(enum.Enum): + REMOTE_URL = "remote_url" + LOCAL_FILE = "local_file" + TOOL_FILE = "tool_file" + + @staticmethod + def value_of(value): + for member in FileTransferMethod: + if member.value == value: + return member + raise ValueError(f"No matching enum found for value '{value}'") + + +class FileBelongsTo(enum.Enum): + USER = "user" + ASSISTANT = "assistant" + + @staticmethod + def value_of(value): + for member in FileBelongsTo: + if member.value == value: + return member + raise ValueError(f"No matching enum found for value '{value}'") + + +class FileVar(BaseModel): + id: Optional[str] = None # message file id + tenant_id: str + type: FileType + transfer_method: FileTransferMethod + url: Optional[str] = None # remote url + related_id: Optional[str] = None + extra_config: Optional[FileExtraConfig] = None + filename: Optional[str] = None + extension: Optional[str] = None + mime_type: Optional[str] = None + + def to_dict(self) -> dict: + return { + "__variant": self.__class__.__name__, + "tenant_id": self.tenant_id, + "type": self.type.value, + "transfer_method": self.transfer_method.value, + "url": self.preview_url, + "remote_url": self.url, + "related_id": self.related_id, + "filename": self.filename, + "extension": self.extension, + "mime_type": self.mime_type, + } + + def to_markdown(self) -> str: + """ + Convert file to markdown + :return: + """ + preview_url = self.preview_url + if self.type == FileType.IMAGE: + text = f'![{self.filename or ""}]({preview_url})' + else: + text = f"[{self.filename or preview_url}]({preview_url})" + + return text + + @property + def data(self) -> Optional[str]: + """ + Get image data, file signed url or base64 data + depending on config MULTIMODAL_SEND_IMAGE_FORMAT + :return: + """ + return self._get_data() + + @property + def preview_url(self) -> Optional[str]: + """ + Get signed preview url + :return: + """ + return self._get_data(force_url=True) + + @property + def prompt_message_content(self) -> ImagePromptMessageContent: + if self.type == FileType.IMAGE: + image_config = self.extra_config.image_config + + return ImagePromptMessageContent( + data=self.data, + detail=ImagePromptMessageContent.DETAIL.HIGH + if image_config.get("detail") == "high" + else ImagePromptMessageContent.DETAIL.LOW, + ) + + def _get_data(self, force_url: bool = False) -> Optional[str]: + from models.model import UploadFile + + if self.type == FileType.IMAGE: + if self.transfer_method == FileTransferMethod.REMOTE_URL: + return self.url + elif self.transfer_method == FileTransferMethod.LOCAL_FILE: + upload_file = ( + db.session.query(UploadFile) + .filter(UploadFile.id == self.related_id, UploadFile.tenant_id == self.tenant_id) + .first() + ) + + return UploadFileParser.get_image_data(upload_file=upload_file, force_url=force_url) + elif self.transfer_method == FileTransferMethod.TOOL_FILE: + extension = self.extension + # add sign url + return ToolFileParser.get_tool_file_manager().sign_file( + tool_file_id=self.related_id, extension=extension + ) + + return None diff --git a/api/core/file/message_file_parser.py b/api/core/file/message_file_parser.py new file mode 100644 index 0000000000..641686bd7c --- /dev/null +++ b/api/core/file/message_file_parser.py @@ -0,0 +1,243 @@ +import re +from collections.abc import Mapping, Sequence +from typing import Any, Union +from urllib.parse import parse_qs, urlparse + +import requests + +from core.file.file_obj import FileBelongsTo, FileExtraConfig, FileTransferMethod, FileType, FileVar +from extensions.ext_database import db +from models.account import Account +from models.model import EndUser, MessageFile, UploadFile +from services.file_service import IMAGE_EXTENSIONS + + +class MessageFileParser: + def __init__(self, tenant_id: str, app_id: str) -> None: + self.tenant_id = tenant_id + self.app_id = app_id + + def validate_and_transform_files_arg( + self, files: Sequence[Mapping[str, Any]], file_extra_config: FileExtraConfig, user: Union[Account, EndUser] + ) -> list[FileVar]: + """ + validate and transform files arg + + :param files: + :param file_extra_config: + :param user: + :return: + """ + for file in files: + if not isinstance(file, dict): + raise ValueError("Invalid file format, must be dict") + if not file.get("type"): + raise ValueError("Missing file type") + FileType.value_of(file.get("type")) + if not file.get("transfer_method"): + raise ValueError("Missing file transfer method") + FileTransferMethod.value_of(file.get("transfer_method")) + if file.get("transfer_method") == FileTransferMethod.REMOTE_URL.value: + if not file.get("url"): + raise ValueError("Missing file url") + if not file.get("url").startswith("http"): + raise ValueError("Invalid file url") + if file.get("transfer_method") == FileTransferMethod.LOCAL_FILE.value and not file.get("upload_file_id"): + raise ValueError("Missing file upload_file_id") + if file.get("transform_method") == FileTransferMethod.TOOL_FILE.value and not file.get("tool_file_id"): + raise ValueError("Missing file tool_file_id") + + # transform files to file objs + type_file_objs = self._to_file_objs(files, file_extra_config) + + # validate files + new_files = [] + for file_type, file_objs in type_file_objs.items(): + if file_type == FileType.IMAGE: + # parse and validate files + image_config = file_extra_config.image_config + + # check if image file feature is enabled + if not image_config: + continue + + # Validate number of files + if len(files) > image_config["number_limits"]: + raise ValueError(f"Number of image files exceeds the maximum limit {image_config['number_limits']}") + + for file_obj in file_objs: + # Validate transfer method + if file_obj.transfer_method.value not in image_config["transfer_methods"]: + raise ValueError(f"Invalid transfer method: {file_obj.transfer_method.value}") + + # Validate file type + if file_obj.type != FileType.IMAGE: + raise ValueError(f"Invalid file type: {file_obj.type}") + + if file_obj.transfer_method == FileTransferMethod.REMOTE_URL: + # check remote url valid and is image + result, error = self._check_image_remote_url(file_obj.url) + if result is False: + raise ValueError(error) + elif file_obj.transfer_method == FileTransferMethod.LOCAL_FILE: + # get upload file from upload_file_id + upload_file = ( + db.session.query(UploadFile) + .filter( + UploadFile.id == file_obj.related_id, + UploadFile.tenant_id == self.tenant_id, + UploadFile.created_by == user.id, + UploadFile.created_by_role == ("account" if isinstance(user, Account) else "end_user"), + UploadFile.extension.in_(IMAGE_EXTENSIONS), + ) + .first() + ) + + # check upload file is belong to tenant and user + if not upload_file: + raise ValueError("Invalid upload file") + + new_files.append(file_obj) + + # return all file objs + return new_files + + def transform_message_files(self, files: list[MessageFile], file_extra_config: FileExtraConfig): + """ + transform message files + + :param files: + :param file_extra_config: + :return: + """ + # transform files to file objs + type_file_objs = self._to_file_objs(files, file_extra_config) + + # return all file objs + return [file_obj for file_objs in type_file_objs.values() for file_obj in file_objs] + + def _to_file_objs( + self, files: list[Union[dict, MessageFile]], file_extra_config: FileExtraConfig + ) -> dict[FileType, list[FileVar]]: + """ + transform files to file objs + + :param files: + :param file_extra_config: + :return: + """ + type_file_objs: dict[FileType, list[FileVar]] = { + # Currently only support image + FileType.IMAGE: [] + } + + if not files: + return type_file_objs + + # group by file type and convert file args or message files to FileObj + for file in files: + if isinstance(file, MessageFile): + if file.belongs_to == FileBelongsTo.ASSISTANT.value: + continue + + file_obj = self._to_file_obj(file, file_extra_config) + if file_obj.type not in type_file_objs: + continue + + type_file_objs[file_obj.type].append(file_obj) + + return type_file_objs + + def _to_file_obj(self, file: Union[dict, MessageFile], file_extra_config: FileExtraConfig): + """ + transform file to file obj + + :param file: + :return: + """ + if isinstance(file, dict): + transfer_method = FileTransferMethod.value_of(file.get("transfer_method")) + if transfer_method != FileTransferMethod.TOOL_FILE: + return FileVar( + tenant_id=self.tenant_id, + type=FileType.value_of(file.get("type")), + transfer_method=transfer_method, + url=file.get("url") if transfer_method == FileTransferMethod.REMOTE_URL else None, + related_id=file.get("upload_file_id") if transfer_method == FileTransferMethod.LOCAL_FILE else None, + extra_config=file_extra_config, + ) + return FileVar( + tenant_id=self.tenant_id, + type=FileType.value_of(file.get("type")), + transfer_method=transfer_method, + url=None, + related_id=file.get("tool_file_id"), + extra_config=file_extra_config, + ) + else: + return FileVar( + id=file.id, + tenant_id=self.tenant_id, + type=FileType.value_of(file.type), + transfer_method=FileTransferMethod.value_of(file.transfer_method), + url=file.url, + related_id=file.upload_file_id or None, + extra_config=file_extra_config, + ) + + def _check_image_remote_url(self, url): + try: + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" + " Chrome/91.0.4472.124 Safari/537.36" + } + + def is_s3_presigned_url(url): + try: + parsed_url = urlparse(url) + if "amazonaws.com" not in parsed_url.netloc: + return False + query_params = parse_qs(parsed_url.query) + + def check_presign_v2(query_params): + required_params = ["Signature", "Expires"] + for param in required_params: + if param not in query_params: + return False + if not query_params["Expires"][0].isdigit(): + return False + signature = query_params["Signature"][0] + if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", signature): + return False + + return True + + def check_presign_v4(query_params): + required_params = ["X-Amz-Signature", "X-Amz-Expires"] + for param in required_params: + if param not in query_params: + return False + if not query_params["X-Amz-Expires"][0].isdigit(): + return False + signature = query_params["X-Amz-Signature"][0] + if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", signature): + return False + + return True + + return check_presign_v4(query_params) or check_presign_v2(query_params) + except Exception: + return False + + if is_s3_presigned_url(url): + response = requests.get(url, headers=headers, allow_redirects=True) + if response.status_code in {200, 304}: + return True, "" + + response = requests.head(url, headers=headers, allow_redirects=True) + if response.status_code in {200, 304}: + return True, "" + else: + return False, "URL does not exist." + except requests.RequestException as e: + return False, f"Error checking URL: {e}" diff --git a/api/core/file/upload_file_parser.py b/api/core/file/upload_file_parser.py new file mode 100644 index 0000000000..a8c1fd4d02 --- /dev/null +++ b/api/core/file/upload_file_parser.py @@ -0,0 +1,79 @@ +import base64 +import hashlib +import hmac +import logging +import os +import time +from typing import Optional + +from configs import dify_config +from extensions.ext_storage import storage + +IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "webp", "gif", "svg"] +IMAGE_EXTENSIONS.extend([ext.upper() for ext in IMAGE_EXTENSIONS]) + + +class UploadFileParser: + @classmethod + def get_image_data(cls, upload_file, force_url: bool = False) -> Optional[str]: + if not upload_file: + return None + + if upload_file.extension not in IMAGE_EXTENSIONS: + return None + + if dify_config.MULTIMODAL_SEND_IMAGE_FORMAT == "url" or force_url: + return cls.get_signed_temp_image_url(upload_file.id) + else: + # get image file base64 + try: + data = storage.load(upload_file.key) + except FileNotFoundError: + logging.error(f"File not found: {upload_file.key}") + return None + + encoded_string = base64.b64encode(data).decode("utf-8") + return f"data:{upload_file.mime_type};base64,{encoded_string}" + + @classmethod + def get_signed_temp_image_url(cls, upload_file_id) -> str: + """ + get signed url from upload file + + :param upload_file: UploadFile object + :return: + """ + base_url = dify_config.FILES_URL + image_preview_url = f"{base_url}/files/{upload_file_id}/image-preview" + + timestamp = str(int(time.time())) + nonce = os.urandom(16).hex() + data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}" + secret_key = dify_config.SECRET_KEY.encode() + sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + encoded_sign = base64.urlsafe_b64encode(sign).decode() + + return f"{image_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" + + @classmethod + def verify_image_file_signature(cls, upload_file_id: str, timestamp: str, nonce: str, sign: str) -> bool: + """ + verify signature + + :param upload_file_id: file id + :param timestamp: timestamp + :param nonce: nonce + :param sign: signature + :return: + """ + data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}" + secret_key = dify_config.SECRET_KEY.encode() + recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() + + # verify signature + if sign != recalculated_encoded_sign: + return False + + current_time = int(time.time()) + return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT diff --git a/api/core/tools/provider/builtin/feishu_base/_assets/icon.svg b/api/core/tools/provider/builtin/feishu_base/_assets/icon.svg new file mode 100644 index 0000000000..2663a0f59e --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/_assets/icon.svg @@ -0,0 +1,47 @@ + + + + diff --git a/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.py b/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.py new file mode 100644 index 0000000000..4a605fbffe --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.py @@ -0,0 +1,56 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class AddBaseRecordTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_id = tool_parameters.get("table_id", "") + if not table_id: + return self.create_text_message("Invalid parameter table_id") + + fields = tool_parameters.get("fields", "") + if not fields: + return self.create_text_message("Invalid parameter fields") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = {} + payload = {"fields": json.loads(fields)} + + try: + res = httpx.post( + url.format(app_token=app_token, table_id=table_id), + headers=headers, + params=params, + json=payload, + timeout=30, + ) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to add base record, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to add base record. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.yaml b/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.yaml new file mode 100644 index 0000000000..3ce0154efd --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/add_base_record.yaml @@ -0,0 +1,66 @@ +identity: + name: add_base_record + author: Doug Lea + label: + en_US: Add Base Record + zh_Hans: 在多维表格数据表中新增一条记录 +description: + human: + en_US: Add Base Record + zh_Hans: | + 在多维表格数据表中新增一条记录,详细请参考:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table-record/create + llm: Add a new record in the multidimensional table data table. +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_id + type: string + required: true + label: + en_US: table_id + zh_Hans: 多维表格的数据表 + human_description: + en_US: bitable table id + zh_Hans: 多维表格数据表的唯一标识符 table_id + llm_description: bitable table id + form: llm + + - name: fields + type: string + required: true + label: + en_US: fields + zh_Hans: 数据表的列字段内容 + human_description: + en_US: The fields of the Base data table are the columns of the data table. + zh_Hans: | + 要增加一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"} + 当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。 + 不同类型字段的数据结构请参考数据结构概述:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure + llm_description: | + 要增加一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"} + 当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。 + 不同类型字段的数据结构请参考数据结构概述:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.py b/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.py new file mode 100644 index 0000000000..b05d700113 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.py @@ -0,0 +1,48 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class CreateBaseTableTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + name = tool_parameters.get("name", "") + + fields = tool_parameters.get("fields", "") + if not fields: + return self.create_text_message("Invalid parameter fields") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = {} + payload = {"table": {"name": name, "fields": json.loads(fields)}} + + try: + res = httpx.post(url.format(app_token=app_token), headers=headers, params=params, json=payload, timeout=30) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to create base table, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to create base table. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.yaml b/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.yaml new file mode 100644 index 0000000000..48c46bec14 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/create_base_table.yaml @@ -0,0 +1,106 @@ +identity: + name: create_base_table + author: Doug Lea + label: + en_US: Create Base Table + zh_Hans: 多维表格新增一个数据表 +description: + human: + en_US: Create base table + zh_Hans: | + 多维表格新增一个数据表,详细请参考:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table/create + llm: A tool for add a new data table to the multidimensional table. +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: name + type: string + required: false + label: + en_US: name + zh_Hans: name + human_description: + en_US: Multidimensional table data table name + zh_Hans: 多维表格数据表名称 + llm_description: Multidimensional table data table name + form: llm + + - name: fields + type: string + required: true + label: + en_US: fields + zh_Hans: fields + human_description: + en_US: Initial fields of the data table + zh_Hans: | + 数据表的初始字段,格式为:[{"field_name":"多行文本","type":1},{"field_name":"数字","type":2},{"field_name":"单选","type":3},{"field_name":"多选","type":4},{"field_name":"日期","type":5}]。 + field_name:字段名; + type: 字段类型;可选值有 + 1:多行文本 + 2:数字 + 3:单选 + 4:多选 + 5:日期 + 7:复选框 + 11:人员 + 13:电话号码 + 15:超链接 + 17:附件 + 18:单向关联 + 20:公式 + 21:双向关联 + 22:地理位置 + 23:群组 + 1001:创建时间 + 1002:最后更新时间 + 1003:创建人 + 1004:修改人 + 1005:自动编号 + llm_description: | + 数据表的初始字段,格式为:[{"field_name":"多行文本","type":1},{"field_name":"数字","type":2},{"field_name":"单选","type":3},{"field_name":"多选","type":4},{"field_name":"日期","type":5}]。 + field_name:字段名; + type: 字段类型;可选值有 + 1:多行文本 + 2:数字 + 3:单选 + 4:多选 + 5:日期 + 7:复选框 + 11:人员 + 13:电话号码 + 15:超链接 + 17:附件 + 18:单向关联 + 20:公式 + 21:双向关联 + 22:地理位置 + 23:群组 + 1001:创建时间 + 1002:最后更新时间 + 1003:创建人 + 1004:修改人 + 1005:自动编号 + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.py b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.py new file mode 100644 index 0000000000..862eb2171b --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.py @@ -0,0 +1,56 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class DeleteBaseRecordsTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_id = tool_parameters.get("table_id", "") + if not table_id: + return self.create_text_message("Invalid parameter table_id") + + record_ids = tool_parameters.get("record_ids", "") + if not record_ids: + return self.create_text_message("Invalid parameter record_ids") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = {} + payload = {"records": json.loads(record_ids)} + + try: + res = httpx.post( + url.format(app_token=app_token, table_id=table_id), + headers=headers, + params=params, + json=payload, + timeout=30, + ) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to delete base records, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to delete base records. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.yaml b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.yaml new file mode 100644 index 0000000000..595b287029 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_records.yaml @@ -0,0 +1,60 @@ +identity: + name: delete_base_records + author: Doug Lea + label: + en_US: Delete Base Records + zh_Hans: 在多维表格数据表中删除多条记录 +description: + human: + en_US: Delete base records + zh_Hans: | + 该接口用于删除多维表格数据表中的多条记录,单次调用中最多删除 500 条记录。 + llm: A tool for delete multiple records in a multidimensional table data table, up to 500 records can be deleted in a single call. +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_id + type: string + required: true + label: + en_US: table_id + zh_Hans: 多维表格的数据表 + human_description: + en_US: bitable table id + zh_Hans: 多维表格数据表的唯一标识符 table_id + llm_description: bitable table id + form: llm + + - name: record_ids + type: string + required: true + label: + en_US: record_ids + zh_Hans: record_ids + human_description: + en_US: A list of multiple record IDs to be deleted, for example ["recwNXzPQv","recpCsf4ME"] + zh_Hans: 待删除的多条记录id列表,示例为 ["recwNXzPQv","recpCsf4ME"] + llm_description: A list of multiple record IDs to be deleted, for example ["recwNXzPQv","recpCsf4ME"] + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.py b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.py new file mode 100644 index 0000000000..f512186303 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.py @@ -0,0 +1,46 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class DeleteBaseTablesTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/batch_delete" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_ids = tool_parameters.get("table_ids", "") + if not table_ids: + return self.create_text_message("Invalid parameter table_ids") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = {} + payload = {"table_ids": json.loads(table_ids)} + + try: + res = httpx.post(url.format(app_token=app_token), headers=headers, params=params, json=payload, timeout=30) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to delete base tables, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to delete base tables. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.yaml b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.yaml new file mode 100644 index 0000000000..5d72814363 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/delete_base_tables.yaml @@ -0,0 +1,48 @@ +identity: + name: delete_base_tables + author: Doug Lea + label: + en_US: Delete Base Tables + zh_Hans: 删除多维表格中的数据表 +description: + human: + en_US: Delete base tables + zh_Hans: | + 删除多维表格中的数据表 + llm: A tool for deleting a data table in a multidimensional table +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_ids + type: string + required: true + label: + en_US: table_ids + zh_Hans: table_ids + human_description: + en_US: The ID list of the data tables to be deleted. Currently, a maximum of 50 data tables can be deleted at a time. The example is ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"] + zh_Hans: 待删除数据表的id列表,当前一次操作最多支持50个数据表,示例为 ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"] + llm_description: The ID list of the data tables to be deleted. Currently, a maximum of 50 data tables can be deleted at a time. The example is ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"] + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.py b/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.py new file mode 100644 index 0000000000..2ea61d0068 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.py @@ -0,0 +1,48 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class GetTenantAccessTokenTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" + + app_id = tool_parameters.get("app_id", "") + if not app_id: + return self.create_text_message("Invalid parameter app_id") + + app_secret = tool_parameters.get("app_secret", "") + if not app_secret: + return self.create_text_message("Invalid parameter app_secret") + + headers = { + "Content-Type": "application/json", + } + params = {} + payload = {"app_id": app_id, "app_secret": app_secret} + + """ + { + "code": 0, + "msg": "ok", + "tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3", + "expire": 7200 + } + """ + try: + res = httpx.post(url, headers=headers, params=params, json=payload, timeout=30) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to get tenant access token, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to get tenant access token. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.yaml b/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.yaml new file mode 100644 index 0000000000..88acc27e06 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/get_tenant_access_token.yaml @@ -0,0 +1,39 @@ +identity: + name: get_tenant_access_token + author: Doug Lea + label: + en_US: Get Tenant Access Token + zh_Hans: 获取飞书自建应用的 tenant_access_token +description: + human: + en_US: Get tenant access token + zh_Hans: | + 获取飞书自建应用的 tenant_access_token,响应体示例: + {"code":0,"msg":"ok","tenant_access_token":"t-caecc734c2e3328a62489fe0648c4b98779515d3","expire":7200} + tenant_access_token: 租户访问凭证; + expire: tenant_access_token 的过期时间,单位为秒; + llm: A tool for obtaining a tenant access token. The input parameters must include app_id and app_secret. +parameters: + - name: app_id + type: string + required: true + label: + en_US: app_id + zh_Hans: 应用唯一标识 + human_description: + en_US: app_id is the unique identifier of the Lark Open Platform application + zh_Hans: app_id 是飞书开放平台应用的唯一标识 + llm_description: app_id is the unique identifier of the Lark Open Platform application + form: llm + + - name: app_secret + type: secret-input + required: true + label: + en_US: app_secret + zh_Hans: 应用秘钥 + human_description: + en_US: app_secret is the secret key of the application + zh_Hans: app_secret 是应用的秘钥 + llm_description: app_secret is the secret key of the application + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.py b/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.py new file mode 100644 index 0000000000..e579d02f69 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.py @@ -0,0 +1,65 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class ListBaseRecordsTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/search" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_id = tool_parameters.get("table_id", "") + if not table_id: + return self.create_text_message("Invalid parameter table_id") + + page_token = tool_parameters.get("page_token", "") + page_size = tool_parameters.get("page_size", "") + sort_condition = tool_parameters.get("sort_condition", "") + filter_condition = tool_parameters.get("filter_condition", "") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = { + "page_token": page_token, + "page_size": page_size, + } + + payload = {"automatic_fields": True} + if sort_condition: + payload["sort"] = json.loads(sort_condition) + if filter_condition: + payload["filter"] = json.loads(filter_condition) + + try: + res = httpx.post( + url.format(app_token=app_token, table_id=table_id), + headers=headers, + params=params, + json=payload, + timeout=30, + ) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to list base records, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to list base records. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.yaml b/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.yaml new file mode 100644 index 0000000000..8647c880a6 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/list_base_records.yaml @@ -0,0 +1,108 @@ +identity: + name: list_base_records + author: Doug Lea + label: + en_US: List Base Records + zh_Hans: 查询多维表格数据表中的现有记录 +description: + human: + en_US: List base records + zh_Hans: | + 查询多维表格数据表中的现有记录,单次最多查询 500 行记录,支持分页获取。 + llm: Query existing records in a multidimensional table data table. A maximum of 500 rows of records can be queried at a time, and paging retrieval is supported. +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_id + type: string + required: true + label: + en_US: table_id + zh_Hans: 多维表格的数据表 + human_description: + en_US: bitable table id + zh_Hans: 多维表格数据表的唯一标识符 table_id + llm_description: bitable table id + form: llm + + - name: page_token + type: string + required: false + label: + en_US: page_token + zh_Hans: 分页标记 + human_description: + en_US: Pagination mark. If it is not filled in the first request, it means to traverse from the beginning. + zh_Hans: 分页标记,第一次请求不填,表示从头开始遍历。 + llm_description: 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果。 + form: llm + + - name: page_size + type: number + required: false + default: 20 + label: + en_US: page_size + zh_Hans: 分页大小 + human_description: + en_US: paging size + zh_Hans: 分页大小,默认值为 20,最大值为 100。 + llm_description: The default value of paging size is 20 and the maximum value is 100. + form: llm + + - name: sort_condition + type: string + required: false + label: + en_US: sort_condition + zh_Hans: 排序条件 + human_description: + en_US: sort condition + zh_Hans: | + 排序条件,格式为:[{"field_name":"多行文本","desc":true}]。 + field_name: 字段名称; + desc: 是否倒序排序; + llm_description: | + Sorting conditions, the format is: [{"field_name":"multi-line text","desc":true}]. + form: llm + + - name: filter_condition + type: string + required: false + label: + en_US: filter_condition + zh_Hans: 筛选条件 + human_description: + en_US: filter condition + zh_Hans: | + 筛选条件,格式为:{"conjunction":"and","conditions":[{"field_name":"字段1","operator":"is","value":["文本内容"]}]}。 + conjunction:条件逻辑连接词; + conditions:筛选条件集合; + field_name:筛选条件的左值,值为字段的名称; + operator:条件运算符; + value:目标值; + llm_description: | + The format of the filter condition is: {"conjunction":"and","conditions":[{"field_name":"Field 1","operator":"is","value":["text content"]}]}. + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.py b/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.py new file mode 100644 index 0000000000..4ec9a476bc --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.py @@ -0,0 +1,47 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class ListBaseTablesTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + page_token = tool_parameters.get("page_token", "") + page_size = tool_parameters.get("page_size", "") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = { + "page_token": page_token, + "page_size": page_size, + } + + try: + res = httpx.get(url.format(app_token=app_token), headers=headers, params=params, timeout=30) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to list base tables, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to list base tables. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.yaml b/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.yaml new file mode 100644 index 0000000000..9887124a28 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/list_base_tables.yaml @@ -0,0 +1,65 @@ +identity: + name: list_base_tables + author: Doug Lea + label: + en_US: List Base Tables + zh_Hans: 根据 app_token 获取多维表格下的所有数据表 +description: + human: + en_US: List base tables + zh_Hans: | + 根据 app_token 获取多维表格下的所有数据表 + llm: A tool for getting all data tables under a multidimensional table based on app_token. +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: page_token + type: string + required: false + label: + en_US: page_token + zh_Hans: 分页标记 + human_description: + en_US: Pagination mark. If it is not filled in the first request, it means to traverse from the beginning. + zh_Hans: 分页标记,第一次请求不填,表示从头开始遍历。 + llm_description: | + Pagination token. If it is not filled in the first request, it means to start traversal from the beginning. + If there are more items in the pagination query result, a new page_token will be returned at the same time. + The page_token can be used to obtain the query result in the next traversal. + 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果。 + form: llm + + - name: page_size + type: number + required: false + default: 20 + label: + en_US: page_size + zh_Hans: 分页大小 + human_description: + en_US: paging size + zh_Hans: 分页大小,默认值为 20,最大值为 100。 + llm_description: The default value of paging size is 20 and the maximum value is 100. + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.py b/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.py new file mode 100644 index 0000000000..fb818f8380 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.py @@ -0,0 +1,49 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class ReadBaseRecordTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_id = tool_parameters.get("table_id", "") + if not table_id: + return self.create_text_message("Invalid parameter table_id") + + record_id = tool_parameters.get("record_id", "") + if not record_id: + return self.create_text_message("Invalid parameter record_id") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + try: + res = httpx.get( + url.format(app_token=app_token, table_id=table_id, record_id=record_id), headers=headers, timeout=30 + ) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to read base record, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to read base record. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.yaml b/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.yaml new file mode 100644 index 0000000000..400e9a1021 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/read_base_record.yaml @@ -0,0 +1,60 @@ +identity: + name: read_base_record + author: Doug Lea + label: + en_US: Read Base Record + zh_Hans: 根据 record_id 的值检索多维表格数据表的记录 +description: + human: + en_US: Read base record + zh_Hans: | + 根据 record_id 的值检索多维表格数据表的记录 + llm: Retrieve records from a multidimensional table based on the value of record_id +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_id + type: string + required: true + label: + en_US: table_id + zh_Hans: 多维表格的数据表 + human_description: + en_US: bitable table id + zh_Hans: 多维表格数据表的唯一标识符 table_id + llm_description: bitable table id + form: llm + + - name: record_id + type: string + required: true + label: + en_US: record_id + zh_Hans: 单条记录的 id + human_description: + en_US: The id of a single record + zh_Hans: 单条记录的 id + llm_description: The id of a single record + form: llm diff --git a/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.py b/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.py new file mode 100644 index 0000000000..6d7e33f3ff --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.py @@ -0,0 +1,60 @@ +import json +from typing import Any, Union + +import httpx + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class UpdateBaseRecordTool(BuiltinTool): + def _invoke( + self, user_id: str, tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}" + + access_token = tool_parameters.get("Authorization", "") + if not access_token: + return self.create_text_message("Invalid parameter access_token") + + app_token = tool_parameters.get("app_token", "") + if not app_token: + return self.create_text_message("Invalid parameter app_token") + + table_id = tool_parameters.get("table_id", "") + if not table_id: + return self.create_text_message("Invalid parameter table_id") + + record_id = tool_parameters.get("record_id", "") + if not record_id: + return self.create_text_message("Invalid parameter record_id") + + fields = tool_parameters.get("fields", "") + if not fields: + return self.create_text_message("Invalid parameter fields") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + } + + params = {} + payload = {"fields": json.loads(fields)} + + try: + res = httpx.put( + url.format(app_token=app_token, table_id=table_id, record_id=record_id), + headers=headers, + params=params, + json=payload, + timeout=30, + ) + res_json = res.json() + if res.is_success: + return self.create_text_message(text=json.dumps(res_json)) + else: + return self.create_text_message( + f"Failed to update base record, status code: {res.status_code}, response: {res.text}" + ) + except Exception as e: + return self.create_text_message("Failed to update base record. {}".format(e)) diff --git a/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.yaml b/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.yaml new file mode 100644 index 0000000000..788798c4b3 --- /dev/null +++ b/api/core/tools/provider/builtin/feishu_base/tools/update_base_record.yaml @@ -0,0 +1,78 @@ +identity: + name: update_base_record + author: Doug Lea + label: + en_US: Update Base Record + zh_Hans: 更新多维表格数据表中的一条记录 +description: + human: + en_US: Update base record + zh_Hans: | + 更新多维表格数据表中的一条记录,详细请参考:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table-record/update + llm: Update a record in a multidimensional table data table +parameters: + - name: Authorization + type: string + required: true + label: + en_US: token + zh_Hans: 凭证 + human_description: + en_US: API access token parameter, tenant_access_token or user_access_token + zh_Hans: API 的访问凭证参数,tenant_access_token 或 user_access_token + llm_description: API access token parameter, tenant_access_token or user_access_token + form: llm + + - name: app_token + type: string + required: true + label: + en_US: app_token + zh_Hans: 多维表格 + human_description: + en_US: bitable app token + zh_Hans: 多维表格的唯一标识符 app_token + llm_description: bitable app token + form: llm + + - name: table_id + type: string + required: true + label: + en_US: table_id + zh_Hans: 多维表格的数据表 + human_description: + en_US: bitable table id + zh_Hans: 多维表格数据表的唯一标识符 table_id + llm_description: bitable table id + form: llm + + - name: record_id + type: string + required: true + label: + en_US: record_id + zh_Hans: 单条记录的 id + human_description: + en_US: The id of a single record + zh_Hans: 单条记录的 id + llm_description: The id of a single record + form: llm + + - name: fields + type: string + required: true + label: + en_US: fields + zh_Hans: 数据表的列字段内容 + human_description: + en_US: The fields of a multidimensional table data table, that is, the columns of the data table. + zh_Hans: | + 要更新一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"} + 当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。 + 不同类型字段的数据结构请参考数据结构概述:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure + llm_description: | + 要更新一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"} + 当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。 + 不同类型字段的数据结构请参考数据结构概述:https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure + form: llm diff --git a/api/core/tools/utils/tool_parameter_converter.py b/api/core/tools/utils/tool_parameter_converter.py new file mode 100644 index 0000000000..6f7610651c --- /dev/null +++ b/api/core/tools/utils/tool_parameter_converter.py @@ -0,0 +1,71 @@ +from typing import Any + +from core.tools.entities.tool_entities import ToolParameter + + +class ToolParameterConverter: + @staticmethod + def get_parameter_type(parameter_type: str | ToolParameter.ToolParameterType) -> str: + match parameter_type: + case ( + ToolParameter.ToolParameterType.STRING + | ToolParameter.ToolParameterType.SECRET_INPUT + | ToolParameter.ToolParameterType.SELECT + ): + return "string" + + case ToolParameter.ToolParameterType.BOOLEAN: + return "boolean" + + case ToolParameter.ToolParameterType.NUMBER: + return "number" + + case _: + raise ValueError(f"Unsupported parameter type {parameter_type}") + + @staticmethod + def cast_parameter_by_type(value: Any, parameter_type: str) -> Any: + # convert tool parameter config to correct type + try: + match parameter_type: + case ( + ToolParameter.ToolParameterType.STRING + | ToolParameter.ToolParameterType.SECRET_INPUT + | ToolParameter.ToolParameterType.SELECT + ): + if value is None: + return "" + else: + return value if isinstance(value, str) else str(value) + + case ToolParameter.ToolParameterType.BOOLEAN: + if value is None: + return False + elif isinstance(value, str): + # Allowed YAML boolean value strings: https://yaml.org/type/bool.html + # and also '0' for False and '1' for True + match value.lower(): + case "true" | "yes" | "y" | "1": + return True + case "false" | "no" | "n" | "0": + return False + case _: + return bool(value) + else: + return value if isinstance(value, bool) else bool(value) + + case ToolParameter.ToolParameterType.NUMBER: + if isinstance(value, int) | isinstance(value, float): + return value + elif isinstance(value, str) and value != "": + if "." in value: + return float(value) + else: + return int(value) + case ToolParameter.ToolParameterType.FILE: + return value + case _: + return str(value) + + except Exception: + raise ValueError(f"The tool parameter value {value} is not in correct type of {parameter_type}.") diff --git a/api/core/workflow/entities/base_node_data_entities.py b/api/core/workflow/entities/base_node_data_entities.py new file mode 100644 index 0000000000..2a864dd7a8 --- /dev/null +++ b/api/core/workflow/entities/base_node_data_entities.py @@ -0,0 +1,24 @@ +from abc import ABC +from typing import Optional + +from pydantic import BaseModel + + +class BaseNodeData(ABC, BaseModel): + title: str + desc: Optional[str] = None + + +class BaseIterationNodeData(BaseNodeData): + start_node_id: Optional[str] = None + + +class BaseIterationState(BaseModel): + iteration_node_id: str + index: int + inputs: dict + + class MetaData(BaseModel): + pass + + metadata: MetaData diff --git a/api/core/workflow/nodes/base_node.py b/api/core/workflow/nodes/base_node.py new file mode 100644 index 0000000000..7bfe45a13c --- /dev/null +++ b/api/core/workflow/nodes/base_node.py @@ -0,0 +1,117 @@ +from abc import ABC, abstractmethod +from collections.abc import Generator, Mapping, Sequence +from typing import Any, Optional + +from core.workflow.entities.base_node_data_entities import BaseNodeData +from core.workflow.entities.node_entities import NodeRunResult, NodeType +from core.workflow.graph_engine.entities.event import InNodeEvent +from core.workflow.graph_engine.entities.graph import Graph +from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams +from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState +from core.workflow.nodes.event import RunCompletedEvent, RunEvent + + +class BaseNode(ABC): + _node_data_cls: type[BaseNodeData] + _node_type: NodeType + + def __init__( + self, + id: str, + config: Mapping[str, Any], + graph_init_params: GraphInitParams, + graph: Graph, + graph_runtime_state: GraphRuntimeState, + previous_node_id: Optional[str] = None, + thread_pool_id: Optional[str] = None, + ) -> None: + self.id = id + self.tenant_id = graph_init_params.tenant_id + self.app_id = graph_init_params.app_id + self.workflow_type = graph_init_params.workflow_type + self.workflow_id = graph_init_params.workflow_id + self.graph_config = graph_init_params.graph_config + self.user_id = graph_init_params.user_id + self.user_from = graph_init_params.user_from + self.invoke_from = graph_init_params.invoke_from + self.workflow_call_depth = graph_init_params.call_depth + self.graph = graph + self.graph_runtime_state = graph_runtime_state + self.previous_node_id = previous_node_id + self.thread_pool_id = thread_pool_id + + node_id = config.get("id") + if not node_id: + raise ValueError("Node ID is required.") + + self.node_id = node_id + self.node_data = self._node_data_cls(**config.get("data", {})) + + @abstractmethod + def _run(self) -> NodeRunResult | Generator[RunEvent | InNodeEvent, None, None]: + """ + Run node + :return: + """ + raise NotImplementedError + + def run(self) -> Generator[RunEvent | InNodeEvent, None, None]: + """ + Run node entry + :return: + """ + result = self._run() + + if isinstance(result, NodeRunResult): + yield RunCompletedEvent(run_result=result) + else: + yield from result + + @classmethod + def extract_variable_selector_to_variable_mapping( + cls, graph_config: Mapping[str, Any], config: dict + ) -> Mapping[str, Sequence[str]]: + """ + Extract variable selector to variable mapping + :param graph_config: graph config + :param config: node config + :return: + """ + node_id = config.get("id") + if not node_id: + raise ValueError("Node ID is required when extracting variable selector to variable mapping.") + + node_data = cls._node_data_cls(**config.get("data", {})) + return cls._extract_variable_selector_to_variable_mapping( + graph_config=graph_config, node_id=node_id, node_data=node_data + ) + + @classmethod + def _extract_variable_selector_to_variable_mapping( + cls, graph_config: Mapping[str, Any], node_id: str, node_data: BaseNodeData + ) -> Mapping[str, Sequence[str]]: + """ + Extract variable selector to variable mapping + :param graph_config: graph config + :param node_id: node id + :param node_data: node data + :return: + """ + return {} + + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return {} + + @property + def node_type(self) -> NodeType: + """ + Get node type + :return: + """ + return self._node_type diff --git a/api/core/workflow/nodes/event.py b/api/core/workflow/nodes/event.py new file mode 100644 index 0000000000..276c13a6d4 --- /dev/null +++ b/api/core/workflow/nodes/event.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field + +from core.workflow.entities.node_entities import NodeRunResult + + +class RunCompletedEvent(BaseModel): + run_result: NodeRunResult = Field(..., description="run result") + + +class RunStreamChunkEvent(BaseModel): + chunk_content: str = Field(..., description="chunk content") + from_variable_selector: list[str] = Field(..., description="from variable selector") + + +class RunRetrieverResourceEvent(BaseModel): + retriever_resources: list[dict] = Field(..., description="retriever resources") + context: str = Field(..., description="context") + + +RunEvent = RunCompletedEvent | RunStreamChunkEvent | RunRetrieverResourceEvent diff --git a/api/core/workflow/nodes/http_request/http_executor.py b/api/core/workflow/nodes/http_request/http_executor.py new file mode 100644 index 0000000000..f8ab4e3132 --- /dev/null +++ b/api/core/workflow/nodes/http_request/http_executor.py @@ -0,0 +1,343 @@ +import json +from copy import deepcopy +from random import randint +from typing import Any, Optional, Union +from urllib.parse import urlencode + +import httpx + +from configs import dify_config +from core.helper import ssrf_proxy +from core.workflow.entities.variable_entities import VariableSelector +from core.workflow.entities.variable_pool import VariablePool +from core.workflow.nodes.http_request.entities import ( + HttpRequestNodeAuthorization, + HttpRequestNodeBody, + HttpRequestNodeData, + HttpRequestNodeTimeout, +) +from core.workflow.utils.variable_template_parser import VariableTemplateParser + + +class HttpExecutorResponse: + headers: dict[str, str] + response: httpx.Response + + def __init__(self, response: httpx.Response): + self.response = response + self.headers = dict(response.headers) if isinstance(self.response, httpx.Response) else {} + + @property + def is_file(self) -> bool: + """ + check if response is file + """ + content_type = self.get_content_type() + file_content_types = ["image", "audio", "video"] + + return any(v in content_type for v in file_content_types) + + def get_content_type(self) -> str: + return self.headers.get("content-type", "") + + def extract_file(self) -> tuple[str, bytes]: + """ + extract file from response if content type is file related + """ + if self.is_file: + return self.get_content_type(), self.body + + return "", b"" + + @property + def content(self) -> str: + if isinstance(self.response, httpx.Response): + return self.response.text + else: + raise ValueError(f"Invalid response type {type(self.response)}") + + @property + def body(self) -> bytes: + if isinstance(self.response, httpx.Response): + return self.response.content + else: + raise ValueError(f"Invalid response type {type(self.response)}") + + @property + def status_code(self) -> int: + if isinstance(self.response, httpx.Response): + return self.response.status_code + else: + raise ValueError(f"Invalid response type {type(self.response)}") + + @property + def size(self) -> int: + return len(self.body) + + @property + def readable_size(self) -> str: + if self.size < 1024: + return f"{self.size} bytes" + elif self.size < 1024 * 1024: + return f"{(self.size / 1024):.2f} KB" + else: + return f"{(self.size / 1024 / 1024):.2f} MB" + + +class HttpExecutor: + server_url: str + method: str + authorization: HttpRequestNodeAuthorization + params: dict[str, Any] + headers: dict[str, Any] + body: Union[None, str] + files: Union[None, dict[str, Any]] + boundary: str + variable_selectors: list[VariableSelector] + timeout: HttpRequestNodeTimeout + + def __init__( + self, + node_data: HttpRequestNodeData, + timeout: HttpRequestNodeTimeout, + variable_pool: Optional[VariablePool] = None, + ): + self.server_url = node_data.url + self.method = node_data.method + self.authorization = node_data.authorization + self.timeout = timeout + self.params = {} + self.headers = {} + self.body = None + self.files = None + + # init template + self.variable_selectors = [] + self._init_template(node_data, variable_pool) + + @staticmethod + def _is_json_body(body: HttpRequestNodeBody): + """ + check if body is json + """ + if body and body.type == "json" and body.data: + try: + json.loads(body.data) + return True + except: + return False + + return False + + @staticmethod + def _to_dict(convert_text: str): + """ + Convert the string like `aa:bb\n cc:dd` to dict `{aa:bb, cc:dd}` + """ + kv_paris = convert_text.split("\n") + result = {} + for kv in kv_paris: + if not kv.strip(): + continue + + kv = kv.split(":", maxsplit=1) + if len(kv) == 1: + k, v = kv[0], "" + else: + k, v = kv + result[k.strip()] = v + return result + + def _init_template(self, node_data: HttpRequestNodeData, variable_pool: Optional[VariablePool] = None): + # extract all template in url + self.server_url, server_url_variable_selectors = self._format_template(node_data.url, variable_pool) + + # extract all template in params + params, params_variable_selectors = self._format_template(node_data.params, variable_pool) + self.params = self._to_dict(params) + + # extract all template in headers + headers, headers_variable_selectors = self._format_template(node_data.headers, variable_pool) + self.headers = self._to_dict(headers) + + # extract all template in body + body_data_variable_selectors = [] + if node_data.body: + # check if it's a valid JSON + is_valid_json = self._is_json_body(node_data.body) + + body_data = node_data.body.data or "" + if body_data: + body_data, body_data_variable_selectors = self._format_template(body_data, variable_pool, is_valid_json) + + content_type_is_set = any(key.lower() == "content-type" for key in self.headers) + if node_data.body.type == "json" and not content_type_is_set: + self.headers["Content-Type"] = "application/json" + elif node_data.body.type == "x-www-form-urlencoded" and not content_type_is_set: + self.headers["Content-Type"] = "application/x-www-form-urlencoded" + + if node_data.body.type in {"form-data", "x-www-form-urlencoded"}: + body = self._to_dict(body_data) + + if node_data.body.type == "form-data": + self.files = {k: ("", v) for k, v in body.items()} + random_str = lambda n: "".join([chr(randint(97, 122)) for _ in range(n)]) + self.boundary = f"----WebKitFormBoundary{random_str(16)}" + + self.headers["Content-Type"] = f"multipart/form-data; boundary={self.boundary}" + else: + self.body = urlencode(body) + elif node_data.body.type in {"json", "raw-text"}: + self.body = body_data + elif node_data.body.type == "none": + self.body = "" + + self.variable_selectors = ( + server_url_variable_selectors + + params_variable_selectors + + headers_variable_selectors + + body_data_variable_selectors + ) + + def _assembling_headers(self) -> dict[str, Any]: + authorization = deepcopy(self.authorization) + headers = deepcopy(self.headers) or {} + if self.authorization.type == "api-key": + if self.authorization.config is None: + raise ValueError("self.authorization config is required") + if authorization.config is None: + raise ValueError("authorization config is required") + + if self.authorization.config.api_key is None: + raise ValueError("api_key is required") + + if not authorization.config.header: + authorization.config.header = "Authorization" + + if self.authorization.config.type == "bearer": + headers[authorization.config.header] = f"Bearer {authorization.config.api_key}" + elif self.authorization.config.type == "basic": + headers[authorization.config.header] = f"Basic {authorization.config.api_key}" + elif self.authorization.config.type == "custom": + headers[authorization.config.header] = authorization.config.api_key + + return headers + + def _validate_and_parse_response(self, response: httpx.Response) -> HttpExecutorResponse: + """ + validate the response + """ + if isinstance(response, httpx.Response): + executor_response = HttpExecutorResponse(response) + else: + raise ValueError(f"Invalid response type {type(response)}") + + threshold_size = ( + dify_config.HTTP_REQUEST_NODE_MAX_BINARY_SIZE + if executor_response.is_file + else dify_config.HTTP_REQUEST_NODE_MAX_TEXT_SIZE + ) + if executor_response.size > threshold_size: + raise ValueError( + f'{"File" if executor_response.is_file else "Text"} size is too large,' + f' max size is {threshold_size / 1024 / 1024:.2f} MB,' + f' but current size is {executor_response.readable_size}.' + ) + + return executor_response + + def _do_http_request(self, headers: dict[str, Any]) -> httpx.Response: + """ + do http request depending on api bundle + """ + kwargs = { + "url": self.server_url, + "headers": headers, + "params": self.params, + "timeout": (self.timeout.connect, self.timeout.read, self.timeout.write), + "follow_redirects": True, + } + + if self.method in {"get", "head", "post", "put", "delete", "patch"}: + response = getattr(ssrf_proxy, self.method)(data=self.body, files=self.files, **kwargs) + else: + raise ValueError(f"Invalid http method {self.method}") + return response + + def invoke(self) -> HttpExecutorResponse: + """ + invoke http request + """ + # assemble headers + headers = self._assembling_headers() + + # do http request + response = self._do_http_request(headers) + + # validate response + return self._validate_and_parse_response(response) + + def to_raw_request(self) -> str: + """ + convert to raw request + """ + server_url = self.server_url + if self.params: + server_url += f"?{urlencode(self.params)}" + + raw_request = f"{self.method.upper()} {server_url} HTTP/1.1\n" + + headers = self._assembling_headers() + for k, v in headers.items(): + # get authorization header + if self.authorization.type == "api-key": + authorization_header = "Authorization" + if self.authorization.config and self.authorization.config.header: + authorization_header = self.authorization.config.header + + if k.lower() == authorization_header.lower(): + raw_request += f'{k}: {"*" * len(v)}\n' + continue + + raw_request += f"{k}: {v}\n" + + raw_request += "\n" + + # if files, use multipart/form-data with boundary + if self.files: + boundary = self.boundary + raw_request += f"--{boundary}" + for k, v in self.files.items(): + raw_request += f'\nContent-Disposition: form-data; name="{k}"\n\n' + raw_request += f"{v[1]}\n" + raw_request += f"--{boundary}" + raw_request += "--" + else: + raw_request += self.body or "" + + return raw_request + + def _format_template( + self, template: str, variable_pool: Optional[VariablePool], escape_quotes: bool = False + ) -> tuple[str, list[VariableSelector]]: + """ + format template + """ + variable_template_parser = VariableTemplateParser(template=template) + variable_selectors = variable_template_parser.extract_variable_selectors() + + if variable_pool: + variable_value_mapping = {} + for variable_selector in variable_selectors: + variable = variable_pool.get_any(variable_selector.value_selector) + if variable is None: + raise ValueError(f"Variable {variable_selector.variable} not found") + if escape_quotes and isinstance(variable, str): + value = variable.replace('"', '\\"').replace("\n", "\\n") + else: + value = variable + variable_value_mapping[variable_selector.variable] = value + + return variable_template_parser.format(variable_value_mapping), variable_selectors + else: + return template, variable_selectors diff --git a/api/core/workflow/nodes/http_request/http_request_node.py b/api/core/workflow/nodes/http_request/http_request_node.py new file mode 100644 index 0000000000..cd40819126 --- /dev/null +++ b/api/core/workflow/nodes/http_request/http_request_node.py @@ -0,0 +1,165 @@ +import logging +from collections.abc import Mapping, Sequence +from mimetypes import guess_extension +from os import path +from typing import Any, cast + +from configs import dify_config +from core.app.segments import parser +from core.file.file_obj import FileTransferMethod, FileType, FileVar +from core.tools.tool_file_manager import ToolFileManager +from core.workflow.entities.node_entities import NodeRunResult, NodeType +from core.workflow.nodes.base_node import BaseNode +from core.workflow.nodes.http_request.entities import ( + HttpRequestNodeData, + HttpRequestNodeTimeout, +) +from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse +from models.workflow import WorkflowNodeExecutionStatus + +HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeTimeout( + connect=dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT, + read=dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT, + write=dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT, +) + + +class HttpRequestNode(BaseNode): + _node_data_cls = HttpRequestNodeData + _node_type = NodeType.HTTP_REQUEST + + @classmethod + def get_default_config(cls, filters: dict | None = None) -> dict: + return { + "type": "http-request", + "config": { + "method": "get", + "authorization": { + "type": "no-auth", + }, + "body": {"type": "none"}, + "timeout": { + **HTTP_REQUEST_DEFAULT_TIMEOUT.model_dump(), + "max_connect_timeout": dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT, + "max_read_timeout": dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT, + "max_write_timeout": dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT, + }, + }, + } + + def _run(self) -> NodeRunResult: + node_data: HttpRequestNodeData = cast(HttpRequestNodeData, self.node_data) + # TODO: Switch to use segment directly + if node_data.authorization.config and node_data.authorization.config.api_key: + node_data.authorization.config.api_key = parser.convert_template( + template=node_data.authorization.config.api_key, variable_pool=self.graph_runtime_state.variable_pool + ).text + + # init http executor + http_executor = None + try: + http_executor = HttpExecutor( + node_data=node_data, + timeout=self._get_request_timeout(node_data), + variable_pool=self.graph_runtime_state.variable_pool, + ) + + # invoke http executor + response = http_executor.invoke() + except Exception as e: + process_data = {} + if http_executor: + process_data = { + "request": http_executor.to_raw_request(), + } + return NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, + error=str(e), + process_data=process_data, + ) + + files = self.extract_files(http_executor.server_url, response) + + return NodeRunResult( + status=WorkflowNodeExecutionStatus.SUCCEEDED, + outputs={ + "status_code": response.status_code, + "body": response.content if not files else "", + "headers": response.headers, + "files": files, + }, + process_data={ + "request": http_executor.to_raw_request(), + }, + ) + + @staticmethod + def _get_request_timeout(node_data: HttpRequestNodeData) -> HttpRequestNodeTimeout: + timeout = node_data.timeout + if timeout is None: + return HTTP_REQUEST_DEFAULT_TIMEOUT + + timeout.connect = timeout.connect or HTTP_REQUEST_DEFAULT_TIMEOUT.connect + timeout.read = timeout.read or HTTP_REQUEST_DEFAULT_TIMEOUT.read + timeout.write = timeout.write or HTTP_REQUEST_DEFAULT_TIMEOUT.write + return timeout + + @classmethod + def _extract_variable_selector_to_variable_mapping( + cls, graph_config: Mapping[str, Any], node_id: str, node_data: HttpRequestNodeData + ) -> Mapping[str, Sequence[str]]: + """ + Extract variable selector to variable mapping + :param graph_config: graph config + :param node_id: node id + :param node_data: node data + :return: + """ + try: + http_executor = HttpExecutor(node_data=node_data, timeout=HTTP_REQUEST_DEFAULT_TIMEOUT) + + variable_selectors = http_executor.variable_selectors + + variable_mapping = {} + for variable_selector in variable_selectors: + variable_mapping[node_id + "." + variable_selector.variable] = variable_selector.value_selector + + return variable_mapping + except Exception as e: + logging.exception(f"Failed to extract variable selector to variable mapping: {e}") + return {} + + def extract_files(self, url: str, response: HttpExecutorResponse) -> list[FileVar]: + """ + Extract files from response + """ + files = [] + mimetype, file_binary = response.extract_file() + + if mimetype: + # extract filename from url + filename = path.basename(url) + # extract extension if possible + extension = guess_extension(mimetype) or ".bin" + + tool_file = ToolFileManager.create_file_by_raw( + user_id=self.user_id, + tenant_id=self.tenant_id, + conversation_id=None, + file_binary=file_binary, + mimetype=mimetype, + ) + + files.append( + FileVar( + tenant_id=self.tenant_id, + type=FileType.IMAGE, + transfer_method=FileTransferMethod.TOOL_FILE, + related_id=tool_file.id, + filename=filename, + extension=extension, + mime_type=mimetype, + ) + ) + + return files diff --git a/api/core/workflow/nodes/llm/llm_node.py b/api/core/workflow/nodes/llm/llm_node.py new file mode 100644 index 0000000000..3d336b0b0b --- /dev/null +++ b/api/core/workflow/nodes/llm/llm_node.py @@ -0,0 +1,774 @@ +import json +from collections.abc import Generator, Mapping, Sequence +from copy import deepcopy +from typing import TYPE_CHECKING, Any, Optional, cast + +from pydantic import BaseModel + +from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity +from core.entities.model_entities import ModelStatus +from core.entities.provider_entities import QuotaUnit +from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError +from core.memory.token_buffer_memory import TokenBufferMemory +from core.model_manager import ModelInstance, ModelManager +from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage +from core.model_runtime.entities.message_entities import ( + ImagePromptMessageContent, + PromptMessage, + PromptMessageContentType, +) +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel +from core.model_runtime.utils.encoders import jsonable_encoder +from core.prompt.advanced_prompt_transform import AdvancedPromptTransform +from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig +from core.prompt.utils.prompt_message_util import PromptMessageUtil +from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType +from core.workflow.entities.variable_pool import VariablePool +from core.workflow.enums import SystemVariableKey +from core.workflow.graph_engine.entities.event import InNodeEvent +from core.workflow.nodes.base_node import BaseNode +from core.workflow.nodes.event import RunCompletedEvent, RunEvent, RunRetrieverResourceEvent, RunStreamChunkEvent +from core.workflow.nodes.llm.entities import ( + LLMNodeChatModelMessage, + LLMNodeCompletionModelPromptTemplate, + LLMNodeData, + ModelConfig, +) +from core.workflow.utils.variable_template_parser import VariableTemplateParser +from extensions.ext_database import db +from models.model import Conversation +from models.provider import Provider, ProviderType +from models.workflow import WorkflowNodeExecutionStatus + +if TYPE_CHECKING: + from core.file.file_obj import FileVar + + +class ModelInvokeCompleted(BaseModel): + """ + Model invoke completed + """ + + text: str + usage: LLMUsage + finish_reason: Optional[str] = None + + +class LLMNode(BaseNode): + _node_data_cls = LLMNodeData + _node_type = NodeType.LLM + + def _run(self) -> Generator[RunEvent | InNodeEvent, None, None]: + """ + Run node + :return: + """ + node_data = cast(LLMNodeData, deepcopy(self.node_data)) + variable_pool = self.graph_runtime_state.variable_pool + + node_inputs = None + process_data = None + + try: + # init messages template + node_data.prompt_template = self._transform_chat_messages(node_data.prompt_template) + + # fetch variables and fetch values from variable pool + inputs = self._fetch_inputs(node_data, variable_pool) + + # fetch jinja2 inputs + jinja_inputs = self._fetch_jinja_inputs(node_data, variable_pool) + + # merge inputs + inputs.update(jinja_inputs) + + node_inputs = {} + + # fetch files + files = self._fetch_files(node_data, variable_pool) + + if files: + node_inputs["#files#"] = [file.to_dict() for file in files] + + # fetch context value + generator = self._fetch_context(node_data, variable_pool) + context = None + for event in generator: + if isinstance(event, RunRetrieverResourceEvent): + context = event.context + yield event + + if context: + node_inputs["#context#"] = context # type: ignore + + # fetch model config + model_instance, model_config = self._fetch_model_config(node_data.model) + + # fetch memory + memory = self._fetch_memory(node_data.memory, variable_pool, model_instance) + + # fetch prompt messages + prompt_messages, stop = self._fetch_prompt_messages( + node_data=node_data, + query=variable_pool.get_any(["sys", SystemVariableKey.QUERY.value]) if node_data.memory else None, + query_prompt_template=node_data.memory.query_prompt_template if node_data.memory else None, + inputs=inputs, + files=files, + context=context, + memory=memory, + model_config=model_config, + ) + + process_data = { + "model_mode": model_config.mode, + "prompts": PromptMessageUtil.prompt_messages_to_prompt_for_saving( + model_mode=model_config.mode, prompt_messages=prompt_messages + ), + "model_provider": model_config.provider, + "model_name": model_config.model, + } + + # handle invoke result + generator = self._invoke_llm( + node_data_model=node_data.model, + model_instance=model_instance, + prompt_messages=prompt_messages, + stop=stop, + ) + + result_text = "" + usage = LLMUsage.empty_usage() + finish_reason = None + for event in generator: + if isinstance(event, RunStreamChunkEvent): + yield event + elif isinstance(event, ModelInvokeCompleted): + result_text = event.text + usage = event.usage + finish_reason = event.finish_reason + break + except Exception as e: + yield RunCompletedEvent( + run_result=NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, + error=str(e), + inputs=node_inputs, + process_data=process_data, + ) + ) + return + + outputs = {"text": result_text, "usage": jsonable_encoder(usage), "finish_reason": finish_reason} + + yield RunCompletedEvent( + run_result=NodeRunResult( + status=WorkflowNodeExecutionStatus.SUCCEEDED, + inputs=node_inputs, + process_data=process_data, + outputs=outputs, + metadata={ + NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, + NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, + NodeRunMetadataKey.CURRENCY: usage.currency, + }, + llm_usage=usage, + ) + ) + + def _invoke_llm( + self, + node_data_model: ModelConfig, + model_instance: ModelInstance, + prompt_messages: list[PromptMessage], + stop: Optional[list[str]] = None, + ) -> Generator[RunEvent | ModelInvokeCompleted, None, None]: + """ + Invoke large language model + :param node_data_model: node data model + :param model_instance: model instance + :param prompt_messages: prompt messages + :param stop: stop + :return: + """ + db.session.close() + + invoke_result = model_instance.invoke_llm( + prompt_messages=prompt_messages, + model_parameters=node_data_model.completion_params, + stop=stop, + stream=True, + user=self.user_id, + ) + + # handle invoke result + generator = self._handle_invoke_result(invoke_result=invoke_result) + + usage = LLMUsage.empty_usage() + for event in generator: + yield event + if isinstance(event, ModelInvokeCompleted): + usage = event.usage + + # deduct quota + self.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) + + def _handle_invoke_result( + self, invoke_result: LLMResult | Generator + ) -> Generator[RunEvent | ModelInvokeCompleted, None, None]: + """ + Handle invoke result + :param invoke_result: invoke result + :return: + """ + if isinstance(invoke_result, LLMResult): + return + + model = None + prompt_messages: list[PromptMessage] = [] + full_text = "" + usage = None + finish_reason = None + for result in invoke_result: + text = result.delta.message.content + full_text += text + + yield RunStreamChunkEvent(chunk_content=text, from_variable_selector=[self.node_id, "text"]) + + if not model: + model = result.model + + if not prompt_messages: + prompt_messages = result.prompt_messages + + if not usage and result.delta.usage: + usage = result.delta.usage + + if not finish_reason and result.delta.finish_reason: + finish_reason = result.delta.finish_reason + + if not usage: + usage = LLMUsage.empty_usage() + + yield ModelInvokeCompleted(text=full_text, usage=usage, finish_reason=finish_reason) + + def _transform_chat_messages( + self, messages: list[LLMNodeChatModelMessage] | LLMNodeCompletionModelPromptTemplate + ) -> list[LLMNodeChatModelMessage] | LLMNodeCompletionModelPromptTemplate: + """ + Transform chat messages + + :param messages: chat messages + :return: + """ + + if isinstance(messages, LLMNodeCompletionModelPromptTemplate): + if messages.edition_type == "jinja2" and messages.jinja2_text: + messages.text = messages.jinja2_text + + return messages + + for message in messages: + if message.edition_type == "jinja2" and message.jinja2_text: + message.text = message.jinja2_text + + return messages + + def _fetch_jinja_inputs(self, node_data: LLMNodeData, variable_pool: VariablePool) -> dict[str, str]: + """ + Fetch jinja inputs + :param node_data: node data + :param variable_pool: variable pool + :return: + """ + variables = {} + + if not node_data.prompt_config: + return variables + + for variable_selector in node_data.prompt_config.jinja2_variables or []: + variable = variable_selector.variable + value = variable_pool.get_any(variable_selector.value_selector) + + def parse_dict(d: dict) -> str: + """ + Parse dict into string + """ + # check if it's a context structure + if "metadata" in d and "_source" in d["metadata"] and "content" in d: + return d["content"] + + # else, parse the dict + try: + return json.dumps(d, ensure_ascii=False) + except Exception: + return str(d) + + if isinstance(value, str): + value = value + elif isinstance(value, list): + result = "" + for item in value: + if isinstance(item, dict): + result += parse_dict(item) + elif isinstance(item, str): + result += item + elif isinstance(item, int | float): + result += str(item) + else: + result += str(item) + result += "\n" + value = result.strip() + elif isinstance(value, dict): + value = parse_dict(value) + elif isinstance(value, int | float): + value = str(value) + else: + value = str(value) + + variables[variable] = value + + return variables + + def _fetch_inputs(self, node_data: LLMNodeData, variable_pool: VariablePool) -> dict[str, str]: + """ + Fetch inputs + :param node_data: node data + :param variable_pool: variable pool + :return: + """ + inputs = {} + prompt_template = node_data.prompt_template + + variable_selectors = [] + if isinstance(prompt_template, list): + for prompt in prompt_template: + variable_template_parser = VariableTemplateParser(template=prompt.text) + variable_selectors.extend(variable_template_parser.extract_variable_selectors()) + elif isinstance(prompt_template, CompletionModelPromptTemplate): + variable_template_parser = VariableTemplateParser(template=prompt_template.text) + variable_selectors = variable_template_parser.extract_variable_selectors() + + for variable_selector in variable_selectors: + variable_value = variable_pool.get_any(variable_selector.value_selector) + if variable_value is None: + raise ValueError(f"Variable {variable_selector.variable} not found") + + inputs[variable_selector.variable] = variable_value + + memory = node_data.memory + if memory and memory.query_prompt_template: + query_variable_selectors = VariableTemplateParser( + template=memory.query_prompt_template + ).extract_variable_selectors() + for variable_selector in query_variable_selectors: + variable_value = variable_pool.get_any(variable_selector.value_selector) + if variable_value is None: + raise ValueError(f"Variable {variable_selector.variable} not found") + + inputs[variable_selector.variable] = variable_value + + return inputs + + def _fetch_files(self, node_data: LLMNodeData, variable_pool: VariablePool) -> list["FileVar"]: + """ + Fetch files + :param node_data: node data + :param variable_pool: variable pool + :return: + """ + if not node_data.vision.enabled: + return [] + + files = variable_pool.get_any(["sys", SystemVariableKey.FILES.value]) + if not files: + return [] + + return files + + def _fetch_context(self, node_data: LLMNodeData, variable_pool: VariablePool) -> Generator[RunEvent, None, None]: + """ + Fetch context + :param node_data: node data + :param variable_pool: variable pool + :return: + """ + if not node_data.context.enabled: + return + + if not node_data.context.variable_selector: + return + + context_value = variable_pool.get_any(node_data.context.variable_selector) + if context_value: + if isinstance(context_value, str): + yield RunRetrieverResourceEvent(retriever_resources=[], context=context_value) + elif isinstance(context_value, list): + context_str = "" + original_retriever_resource = [] + for item in context_value: + if isinstance(item, str): + context_str += item + "\n" + else: + if "content" not in item: + raise ValueError(f"Invalid context structure: {item}") + + context_str += item["content"] + "\n" + + retriever_resource = self._convert_to_original_retriever_resource(item) + if retriever_resource: + original_retriever_resource.append(retriever_resource) + + yield RunRetrieverResourceEvent( + retriever_resources=original_retriever_resource, context=context_str.strip() + ) + + def _convert_to_original_retriever_resource(self, context_dict: dict) -> Optional[dict]: + """ + Convert to original retriever resource, temp. + :param context_dict: context dict + :return: + """ + if ( + "metadata" in context_dict + and "_source" in context_dict["metadata"] + and context_dict["metadata"]["_source"] == "knowledge" + ): + metadata = context_dict.get("metadata", {}) + + source = { + "position": metadata.get("position"), + "dataset_id": metadata.get("dataset_id"), + "dataset_name": metadata.get("dataset_name"), + "document_id": metadata.get("document_id"), + "document_name": metadata.get("document_name"), + "data_source_type": metadata.get("document_data_source_type"), + "segment_id": metadata.get("segment_id"), + "retriever_from": metadata.get("retriever_from"), + "score": metadata.get("score"), + "hit_count": metadata.get("segment_hit_count"), + "word_count": metadata.get("segment_word_count"), + "segment_position": metadata.get("segment_position"), + "index_node_hash": metadata.get("segment_index_node_hash"), + "content": context_dict.get("content"), + } + + return source + + return None + + def _fetch_model_config( + self, node_data_model: ModelConfig + ) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: + """ + Fetch model config + :param node_data_model: node data model + :return: + """ + model_name = node_data_model.name + provider_name = node_data_model.provider + + model_manager = ModelManager() + model_instance = model_manager.get_model_instance( + tenant_id=self.tenant_id, model_type=ModelType.LLM, provider=provider_name, model=model_name + ) + + provider_model_bundle = model_instance.provider_model_bundle + model_type_instance = model_instance.model_type_instance + model_type_instance = cast(LargeLanguageModel, model_type_instance) + + model_credentials = model_instance.credentials + + # check model + provider_model = provider_model_bundle.configuration.get_provider_model( + model=model_name, model_type=ModelType.LLM + ) + + if provider_model is None: + raise ValueError(f"Model {model_name} not exist.") + + if provider_model.status == ModelStatus.NO_CONFIGURE: + raise ProviderTokenNotInitError(f"Model {model_name} credentials is not initialized.") + elif provider_model.status == ModelStatus.NO_PERMISSION: + raise ModelCurrentlyNotSupportError(f"Dify Hosted OpenAI {model_name} currently not support.") + elif provider_model.status == ModelStatus.QUOTA_EXCEEDED: + raise QuotaExceededError(f"Model provider {provider_name} quota exceeded.") + + # model config + completion_params = node_data_model.completion_params + stop = [] + if "stop" in completion_params: + stop = completion_params["stop"] + del completion_params["stop"] + + # get model mode + model_mode = node_data_model.mode + if not model_mode: + raise ValueError("LLM mode is required.") + + model_schema = model_type_instance.get_model_schema(model_name, model_credentials) + + if not model_schema: + raise ValueError(f"Model {model_name} not exist.") + + return model_instance, ModelConfigWithCredentialsEntity( + provider=provider_name, + model=model_name, + model_schema=model_schema, + mode=model_mode, + provider_model_bundle=provider_model_bundle, + credentials=model_credentials, + parameters=completion_params, + stop=stop, + ) + + def _fetch_memory( + self, node_data_memory: Optional[MemoryConfig], variable_pool: VariablePool, model_instance: ModelInstance + ) -> Optional[TokenBufferMemory]: + """ + Fetch memory + :param node_data_memory: node data memory + :param variable_pool: variable pool + :return: + """ + if not node_data_memory: + return None + + # get conversation id + conversation_id = variable_pool.get_any(["sys", SystemVariableKey.CONVERSATION_ID.value]) + if conversation_id is None: + return None + + # get conversation + conversation = ( + db.session.query(Conversation) + .filter(Conversation.app_id == self.app_id, Conversation.id == conversation_id) + .first() + ) + + if not conversation: + return None + + memory = TokenBufferMemory(conversation=conversation, model_instance=model_instance) + + return memory + + def _fetch_prompt_messages( + self, + node_data: LLMNodeData, + query: Optional[str], + query_prompt_template: Optional[str], + inputs: dict[str, str], + files: list["FileVar"], + context: Optional[str], + memory: Optional[TokenBufferMemory], + model_config: ModelConfigWithCredentialsEntity, + ) -> tuple[list[PromptMessage], Optional[list[str]]]: + """ + Fetch prompt messages + :param node_data: node data + :param query: query + :param query_prompt_template: query prompt template + :param inputs: inputs + :param files: files + :param context: context + :param memory: memory + :param model_config: model config + :return: + """ + prompt_transform = AdvancedPromptTransform(with_variable_tmpl=True) + prompt_messages = prompt_transform.get_prompt( + prompt_template=node_data.prompt_template, + inputs=inputs, + query=query or "", + files=files, + context=context, + memory_config=node_data.memory, + memory=memory, + model_config=model_config, + query_prompt_template=query_prompt_template, + ) + stop = model_config.stop + + vision_enabled = node_data.vision.enabled + vision_detail = node_data.vision.configs.detail if node_data.vision.configs else None + filtered_prompt_messages = [] + for prompt_message in prompt_messages: + if prompt_message.is_empty(): + continue + + if not isinstance(prompt_message.content, str): + prompt_message_content = [] + for content_item in prompt_message.content: + if ( + vision_enabled + and content_item.type == PromptMessageContentType.IMAGE + and isinstance(content_item, ImagePromptMessageContent) + ): + # Override vision config if LLM node has vision config + if vision_detail: + content_item.detail = ImagePromptMessageContent.DETAIL(vision_detail) + prompt_message_content.append(content_item) + elif content_item.type == PromptMessageContentType.TEXT: + prompt_message_content.append(content_item) + + if len(prompt_message_content) > 1: + prompt_message.content = prompt_message_content + elif ( + len(prompt_message_content) == 1 and prompt_message_content[0].type == PromptMessageContentType.TEXT + ): + prompt_message.content = prompt_message_content[0].data + + filtered_prompt_messages.append(prompt_message) + + if not filtered_prompt_messages: + raise ValueError( + "No prompt found in the LLM configuration. " + "Please ensure a prompt is properly configured before proceeding." + ) + + return filtered_prompt_messages, stop + + @classmethod + def deduct_llm_quota(cls, tenant_id: str, model_instance: ModelInstance, usage: LLMUsage) -> None: + """ + Deduct LLM quota + :param tenant_id: tenant id + :param model_instance: model instance + :param usage: usage + :return: + """ + provider_model_bundle = model_instance.provider_model_bundle + provider_configuration = provider_model_bundle.configuration + + if provider_configuration.using_provider_type != ProviderType.SYSTEM: + return + + system_configuration = provider_configuration.system_configuration + + quota_unit = None + for quota_configuration in system_configuration.quota_configurations: + if quota_configuration.quota_type == system_configuration.current_quota_type: + quota_unit = quota_configuration.quota_unit + + if quota_configuration.quota_limit == -1: + return + + break + + used_quota = None + if quota_unit: + if quota_unit == QuotaUnit.TOKENS: + used_quota = usage.total_tokens + elif quota_unit == QuotaUnit.CREDITS: + used_quota = 1 + + if "gpt-4" in model_instance.model: + used_quota = 20 + else: + used_quota = 1 + + if used_quota is not None: + db.session.query(Provider).filter( + Provider.tenant_id == tenant_id, + Provider.provider_name == model_instance.provider, + Provider.provider_type == ProviderType.SYSTEM.value, + Provider.quota_type == system_configuration.current_quota_type.value, + Provider.quota_limit > Provider.quota_used, + ).update({"quota_used": Provider.quota_used + used_quota}) + db.session.commit() + + @classmethod + def _extract_variable_selector_to_variable_mapping( + cls, graph_config: Mapping[str, Any], node_id: str, node_data: LLMNodeData + ) -> Mapping[str, Sequence[str]]: + """ + Extract variable selector to variable mapping + :param graph_config: graph config + :param node_id: node id + :param node_data: node data + :return: + """ + prompt_template = node_data.prompt_template + + variable_selectors = [] + if isinstance(prompt_template, list): + for prompt in prompt_template: + if prompt.edition_type != "jinja2": + variable_template_parser = VariableTemplateParser(template=prompt.text) + variable_selectors.extend(variable_template_parser.extract_variable_selectors()) + else: + if prompt_template.edition_type != "jinja2": + variable_template_parser = VariableTemplateParser(template=prompt_template.text) + variable_selectors = variable_template_parser.extract_variable_selectors() + + variable_mapping = {} + for variable_selector in variable_selectors: + variable_mapping[variable_selector.variable] = variable_selector.value_selector + + memory = node_data.memory + if memory and memory.query_prompt_template: + query_variable_selectors = VariableTemplateParser( + template=memory.query_prompt_template + ).extract_variable_selectors() + for variable_selector in query_variable_selectors: + variable_mapping[variable_selector.variable] = variable_selector.value_selector + + if node_data.context.enabled: + variable_mapping["#context#"] = node_data.context.variable_selector + + if node_data.vision.enabled: + variable_mapping["#files#"] = ["sys", SystemVariableKey.FILES.value] + + if node_data.memory: + variable_mapping["#sys.query#"] = ["sys", SystemVariableKey.QUERY.value] + + if node_data.prompt_config: + enable_jinja = False + + if isinstance(prompt_template, list): + for prompt in prompt_template: + if prompt.edition_type == "jinja2": + enable_jinja = True + break + else: + if prompt_template.edition_type == "jinja2": + enable_jinja = True + + if enable_jinja: + for variable_selector in node_data.prompt_config.jinja2_variables or []: + variable_mapping[variable_selector.variable] = variable_selector.value_selector + + variable_mapping = {node_id + "." + key: value for key, value in variable_mapping.items()} + + return variable_mapping + + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return { + "type": "llm", + "config": { + "prompt_templates": { + "chat_model": { + "prompts": [ + {"role": "system", "text": "You are a helpful AI assistant.", "edition_type": "basic"} + ] + }, + "completion_model": { + "conversation_histories_role": {"user_prefix": "Human", "assistant_prefix": "Assistant"}, + "prompt": { + "text": "Here is the chat histories between human and assistant, inside " + " XML tags.\n\n\n{{" + "#histories#}}\n\n\n\nHuman: {{#sys.query#}}\n\nAssistant:", + "edition_type": "basic", + }, + "stop": ["Human:"], + }, + } + }, + } diff --git a/api/tests/unit_tests/core/tools/test_tool_parameter_converter.py b/api/tests/unit_tests/core/tools/test_tool_parameter_converter.py new file mode 100644 index 0000000000..279a6cdbc3 --- /dev/null +++ b/api/tests/unit_tests/core/tools/test_tool_parameter_converter.py @@ -0,0 +1,56 @@ +import pytest + +from core.tools.entities.tool_entities import ToolParameter +from core.tools.utils.tool_parameter_converter import ToolParameterConverter + + +def test_get_parameter_type(): + assert ToolParameterConverter.get_parameter_type(ToolParameter.ToolParameterType.STRING) == "string" + assert ToolParameterConverter.get_parameter_type(ToolParameter.ToolParameterType.SELECT) == "string" + assert ToolParameterConverter.get_parameter_type(ToolParameter.ToolParameterType.BOOLEAN) == "boolean" + assert ToolParameterConverter.get_parameter_type(ToolParameter.ToolParameterType.NUMBER) == "number" + with pytest.raises(ValueError): + ToolParameterConverter.get_parameter_type("unsupported_type") + + +def test_cast_parameter_by_type(): + # string + assert ToolParameterConverter.cast_parameter_by_type("test", ToolParameter.ToolParameterType.STRING) == "test" + assert ToolParameterConverter.cast_parameter_by_type(1, ToolParameter.ToolParameterType.STRING) == "1" + assert ToolParameterConverter.cast_parameter_by_type(1.0, ToolParameter.ToolParameterType.STRING) == "1.0" + assert ToolParameterConverter.cast_parameter_by_type(None, ToolParameter.ToolParameterType.STRING) == "" + + # secret input + assert ToolParameterConverter.cast_parameter_by_type("test", ToolParameter.ToolParameterType.SECRET_INPUT) == "test" + assert ToolParameterConverter.cast_parameter_by_type(1, ToolParameter.ToolParameterType.SECRET_INPUT) == "1" + assert ToolParameterConverter.cast_parameter_by_type(1.0, ToolParameter.ToolParameterType.SECRET_INPUT) == "1.0" + assert ToolParameterConverter.cast_parameter_by_type(None, ToolParameter.ToolParameterType.SECRET_INPUT) == "" + + # select + assert ToolParameterConverter.cast_parameter_by_type("test", ToolParameter.ToolParameterType.SELECT) == "test" + assert ToolParameterConverter.cast_parameter_by_type(1, ToolParameter.ToolParameterType.SELECT) == "1" + assert ToolParameterConverter.cast_parameter_by_type(1.0, ToolParameter.ToolParameterType.SELECT) == "1.0" + assert ToolParameterConverter.cast_parameter_by_type(None, ToolParameter.ToolParameterType.SELECT) == "" + + # boolean + true_values = [True, "True", "true", "1", "YES", "Yes", "yes", "y", "something"] + for value in true_values: + assert ToolParameterConverter.cast_parameter_by_type(value, ToolParameter.ToolParameterType.BOOLEAN) is True + + false_values = [False, "False", "false", "0", "NO", "No", "no", "n", None, ""] + for value in false_values: + assert ToolParameterConverter.cast_parameter_by_type(value, ToolParameter.ToolParameterType.BOOLEAN) is False + + # number + assert ToolParameterConverter.cast_parameter_by_type("1", ToolParameter.ToolParameterType.NUMBER) == 1 + assert ToolParameterConverter.cast_parameter_by_type("1.0", ToolParameter.ToolParameterType.NUMBER) == 1.0 + assert ToolParameterConverter.cast_parameter_by_type("-1.0", ToolParameter.ToolParameterType.NUMBER) == -1.0 + assert ToolParameterConverter.cast_parameter_by_type(1, ToolParameter.ToolParameterType.NUMBER) == 1 + assert ToolParameterConverter.cast_parameter_by_type(1.0, ToolParameter.ToolParameterType.NUMBER) == 1.0 + assert ToolParameterConverter.cast_parameter_by_type(-1.0, ToolParameter.ToolParameterType.NUMBER) == -1.0 + assert ToolParameterConverter.cast_parameter_by_type(None, ToolParameter.ToolParameterType.NUMBER) is None + + # unknown + assert ToolParameterConverter.cast_parameter_by_type("1", "unknown_type") == "1" + assert ToolParameterConverter.cast_parameter_by_type(1, "unknown_type") == "1" + assert ToolParameterConverter.cast_parameter_by_type(None, ToolParameter.ToolParameterType.NUMBER) is None diff --git a/web/app/components/app/configuration/config-var/select-type-item/style.module.css b/web/app/components/app/configuration/config-var/select-type-item/style.module.css new file mode 100644 index 0000000000..8ff716d58b --- /dev/null +++ b/web/app/components/app/configuration/config-var/select-type-item/style.module.css @@ -0,0 +1,40 @@ +.item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 58px; + width: 98px; + border-radius: 8px; + border: 1px solid #EAECF0; + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + background-color: #fff; + cursor: pointer; +} + +.item:not(.selected):hover { + border-color: #B2CCFF; + background-color: #F5F8FF; + box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06); +} + +.item.selected { + color: #155EEF; + border-color: #528BFF; + background-color: #F5F8FF; + box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); +} + +.text { + font-size: 13px; + color: #667085; + font-weight: 500; +} + +.item.selected .text { + color: #155EEF; +} + +.item:not(.selected):hover { + color: #344054; +} \ No newline at end of file diff --git a/web/app/components/app/configuration/config-vision/radio-group/index.tsx b/web/app/components/app/configuration/config-vision/radio-group/index.tsx new file mode 100644 index 0000000000..a1cfb06e6a --- /dev/null +++ b/web/app/components/app/configuration/config-vision/radio-group/index.tsx @@ -0,0 +1,40 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import s from './style.module.css' +import cn from '@/utils/classnames' + +type OPTION = { + label: string + value: any +} + +type Props = { + className?: string + options: OPTION[] + value: any + onChange: (value: any) => void +} + +const RadioGroup: FC = ({ + className = '', + options, + value, + onChange, +}) => { + return ( +
+ {options.map(item => ( +
onChange(item.value)} + > +
+
{item.label}
+
+ ))} +
+ ) +} +export default React.memo(RadioGroup) diff --git a/web/app/components/app/configuration/config-vision/radio-group/style.module.css b/web/app/components/app/configuration/config-vision/radio-group/style.module.css new file mode 100644 index 0000000000..22c29c6a42 --- /dev/null +++ b/web/app/components/app/configuration/config-vision/radio-group/style.module.css @@ -0,0 +1,24 @@ +.item { + @apply grow flex items-center h-8 px-2.5 rounded-lg bg-gray-25 border border-gray-100 cursor-pointer space-x-2; +} + +.item:hover { + background-color: #ffffff; + border-color: #B2CCFF; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); +} + +.item.checked { + background-color: #ffffff; + border-color: #528BFF; + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.10); +} + +.radio { + @apply w-4 h-4 border-[2px] border-gray-200 rounded-full; +} + +.item.checked .radio { + border-width: 5px; + border-color: #155eef; +} \ No newline at end of file diff --git a/web/app/components/app/configuration/config-voice/param-config-content.tsx b/web/app/components/app/configuration/config-voice/param-config-content.tsx new file mode 100644 index 0000000000..4e70bdda21 --- /dev/null +++ b/web/app/components/app/configuration/config-voice/param-config-content.tsx @@ -0,0 +1,220 @@ +'use client' +import useSWR from 'swr' +import type { FC } from 'react' +import { useContext } from 'use-context-selector' +import React, { Fragment } from 'react' +import { usePathname } from 'next/navigation' +import { useTranslation } from 'react-i18next' +import { Listbox, Transition } from '@headlessui/react' +import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' +import classNames from '@/utils/classnames' +import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group' +import type { Item } from '@/app/components/base/select' +import ConfigContext from '@/context/debug-configuration' +import { fetchAppVoices } from '@/service/apps' +import Tooltip from '@/app/components/base/tooltip' +import { languages } from '@/i18n/language' +import { TtsAutoPlay } from '@/types/app' +const VoiceParamConfig: FC = () => { + const { t } = useTranslation() + const pathname = usePathname() + const matched = pathname.match(/\/app\/([^/]+)/) + const appId = (matched?.length && matched[1]) ? matched[1] : '' + + const { + textToSpeechConfig, + setTextToSpeechConfig, + } = useContext(ConfigContext) + + let languageItem = languages.find(item => item.value === textToSpeechConfig.language) + const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') + if (languages && !languageItem && languages.length > 0) + languageItem = languages[0] + const language = languageItem?.value + const voiceItems = useSWR({ appId, language }, fetchAppVoices).data + let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) + if (voiceItems && !voiceItem && voiceItems.length > 0) + voiceItem = voiceItems[0] + + const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') + + return ( +
+
+
{t('appDebug.voice.voiceSettings.title')}
+
+
+
+
{t('appDebug.voice.voiceSettings.language')}
+ + {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => ( +
{item}
+ ))} +
+ } + /> +
+ { + setTextToSpeechConfig({ + ...textToSpeechConfig, + language: String(value.value), + }) + }} + > +
+ + + {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} + + + + + + + + {languages.map((item: Item) => ( + + `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' + }` + } + value={item} + disabled={false} + > + {({ /* active, */ selected }) => ( + <> + {t(`common.voice.language.${(item.value).toString().replace('-', '')}`)} + {(selected || item.value === textToSpeechConfig.language) && ( + + + )} + + )} + + ))} + + +
+
+
+
+
{t('appDebug.voice.voiceSettings.voice')}
+ { + if (!value.value) + return + setTextToSpeechConfig({ + ...textToSpeechConfig, + voice: String(value.value), + }) + }} + > +
+ + {voiceItem?.name ?? localVoicePlaceholder} + + + + + + + {voiceItems?.map((item: Item) => ( + + `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' + }` + } + value={item} + disabled={false} + > + {({ /* active, */ selected }) => ( + <> + {item.name} + {(selected || item.value === textToSpeechConfig.voice) && ( + + + )} + + )} + + ))} + + +
+
+
+
+
{t('appDebug.voice.voiceSettings.autoPlay')}
+ { + setTextToSpeechConfig({ + ...textToSpeechConfig, + autoPlay: value, + }) + }} + /> +
+
+
+ + ) +} + +export default React.memo(VoiceParamConfig) diff --git a/web/app/components/app/configuration/config-voice/param-config.tsx b/web/app/components/app/configuration/config-voice/param-config.tsx new file mode 100644 index 0000000000..f1e2475495 --- /dev/null +++ b/web/app/components/app/configuration/config-voice/param-config.tsx @@ -0,0 +1,41 @@ +'use client' +import type { FC } from 'react' +import { memo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import VoiceParamConfig from './param-config-content' +import cn from '@/utils/classnames' +import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +const ParamsConfig: FC = () => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + return ( + + setOpen(v => !v)}> +
+ +
{t('appDebug.voice.settings')}
+
+
+ +
+ +
+
+
+ ) +} +export default memo(ParamsConfig) diff --git a/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx b/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx new file mode 100644 index 0000000000..eb3edc7593 --- /dev/null +++ b/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx @@ -0,0 +1,40 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { PlusIcon } from '@heroicons/react/24/solid' + +export type IAddFeatureBtnProps = { + toBottomHeight: number + onClick: () => void +} + +const ITEM_HEIGHT = 48 + +const AddFeatureBtn: FC = ({ + toBottomHeight, + onClick, +}) => { + const { t } = useTranslation() + return ( +
+
+ +
{t('appDebug.operation.addFeature')}
+
+
+ ) +} +export default React.memo(AddFeatureBtn) diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx new file mode 100644 index 0000000000..18623c11c3 --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx @@ -0,0 +1,52 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import s from './style.module.css' +import cn from '@/utils/classnames' +import Switch from '@/app/components/base/switch' + +export type IFeatureItemProps = { + icon: React.ReactNode + previewImgClassName?: string + title: string + description: string + value: boolean + onChange: (value: boolean) => void +} + +const FeatureItem: FC = ({ + icon, + previewImgClassName, + title, + description, + value, + onChange, +}) => { + return ( +
+
+ {/* icon */} +
+ {icon} +
+
+
{title}
+
{description}
+
+
+ + + { + previewImgClassName && ( +
+
) + } +
+ ) +} +export default React.memo(FeatureItem) diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.png new file mode 100644 index 0000000000000000000000000000000000000000..cc0847c942693ac2a2026395c3e6fc86c2dc0626 GIT binary patch literal 29852 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μW73O{l_UeVFQ1ZJfvH!n^l%*g(RX!_ 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 literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.svg b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.svg new file mode 100644 index 0000000000..82fb182a7a --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citation.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/citations-and-attributions-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef066204ca42478c976a0136385221a9464fb9b6 GIT binary patch literal 20827 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 S6Vpuq0000bQ7k~|VfI^{kel1jKD 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 VA{u6Wnt$HVkd;!BEE6{j{4XB~qQ?LL literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..62671c5889edba480d8454d5e767db9482772b33 GIT binary patch literal 19839 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 KZ(uZR!~ProepMC# literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this.svg b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this.svg new file mode 100644 index 0000000000..6ccc84e70f --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/more-like-this.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/next-question-suggestion-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/next-question-suggestion-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..758708ff15d33549a0dbcb128debe2393cdb1e30 GIT binary patch literal 28325 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 HCj9>a)KP2< literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text-preview@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..68df3983dc7df3a163fdec8041b7e9ebfc2c3b56 GIT binary patch literal 16929 zcmcJ$1yEa27e5%>wX`?{2wvRXA%OzLN^x2!?nR1gp?Hwe;+Eo4+})kx6nA&mZ2Ila z&hG5)fA&B7ZSpd6bM$x4z3<-h?t3r%t(p=ZHU%~S0KkKQPWDV9?^OPHNFnEr z;umkE7fK^JJEZ;V?89^N*rmAFMG*WatYovN4FEW$Re}Nls8|3%5Gnu&1^@^_2q(yZ z03Z?ollVUzX@tN(4np7`=br$AgAjOPjH;KSswmWt98U=;f>|*YMALoH5XdhY7`c)^ z$0q$JQ?aQqPpmLq()dB>qwa7XWd!&WL~U;86T)JaqF}Z@=WZnn`G@IT7ugh4LQt0D zR7 zxHvY8!L+4Hv%Q-|Do9&v2!McHY$FKv8)JZ5QD4ROH%Ct2sg({5GjX8)M+bpb5IiPv zIg6*~zYzC*@pSDoy{SclMGyi+ zq&SIEnIo=0)ovLKMI`3uC6i-?cixD9?b-Z8;$MuEgJZ`s4r2{xv+Ln-Ifph*kHNgI zu+1#jOikZR&D?L}2ctmdli71bG6`XSO;t)j$w8^wUT zODSF6W{7EDwQe^iIsF78pKxsbDbF?hcFl-Cr?oE9EImB(KvGIxFT-@!6{N(fW zQE4)EJdwI9defslwXy$$^ozrNp7rn_t(1st%6QHFap5FB>)GI-cNu)?YBb(baU1DV z@kNWp=lttg=xA-e&+!{nR-Ihe2=v$f20JRM$e6^K6Un{$VP*DisFF4(&l$NJeCjn) zuShcF4bPd@4UPf{^qShZ`ZABc2on}L+(;Z&e*%F6FP2am6s^yZKJGUm{fqOh@%6L4 zK?Z)ce{ro5&qpKpZQpD41=X3d`?nrO1;rYdK5ea4?_vkf8g`m>7aB+3Cx*?GT`$+x z!)0<5_b?!ZC+N~pX=wG?#tes1Wq#g$oo(%HlY%3taqFznK#*4Sz3$Ciqg&tWB&o86 zef;bGvn6#QQ*CCjlxgNnr_I|w{D(XSQ%e=~&tv_~*$Ib!VlI8}euggs+#;I&?n zrep9fz4c_XXu74!a@qUzG*Q+2^E*d9_w$P8nuR19vCj{+)IF1Vw3#BgaG8Bvca2S@hZV;$D19z)Ka zlK!)SkSz~m18x{BD?mu#GV}2~(_ahaUz860gZEG04it6lsp|Y5=Dh0$K3l)JYq?O0 zP8IubUOm9q2SfL0{&MmyUZTHf+CweSibx7HN-u2-SrzrOzKr?keUq{{YQ zgD1!Css9|WOJ#P4i)}E2pU?kqnv{AmeXg*ZyUEXLjmp9@uba3Hl@;DUF z>sfx;Gl1j3()L3_05PldKaT47^SV9HzIy#+p|PinGwXX*J3U**37@WK-C#eyzX~aQ z!G3ag)otCT|NilN=R`Sal3#Jb=M*pERLa`I3#xu9-1m5N=+C!&>H7<|KGd`IBhg6n*~NtI z_0w*>NG41DFKkX2m47Bbq&Fi)>D`luK zOzxcAKS4d7Md4u^78%Zs#vbSxGKf>cG@gs9V!xMC0OD17@yHYT1Wvfpi7`0LPcI7< zl5EXX724d)OF}|Sf%LB&H3jBlqqIWunB#rl<07(31JdK%O)iboi9J8X1cIeky5mXh zWxrz$Xh)kb+sXD@KQxme(#JnQ8p9%6wXX=LAtP5UV#?3?*R5q~8Q{SrVeOF|n`pXh zOdX~{)Ed144%w=3?FjkTqaMWnlhgRWnG%4aKJ(Rz3J?GnMojZ?^KyK|gfC5od3{o1 z6^(a%i=P=sM-4#b1_5xf{vC42h@%|}KpY6#e~bLjP9Z5El>gyyY1-vLk*e?P9Otr; zOWUHA4Y9`6>3*`Xu)t@waR2#2MSnuS=~caf#rgaunIB(fFb^94%6waUc>YwQ-KdlC zC3y6TiE=b?%;`E!~GoL^YHk z#PpA>SWIc@ff@fqF^^aU`w%APzCJ;Od$aq3w4GY;ZOmIB3dO3OtM~xiwnAuf2G&Fn zA^G4NV)w?^W;lkX2m|h{(dm#DLYN{U3`>A#mzXuS1QOms4Va@|TsxOtq5)C&h|Kbl z@3N84P7a_UmX|2M2R4TFzz5dmdtsgaznSD#%sLepAP?^z1l02 z;J}g@6fH~NisMS>;)+xp3vZM7bGJ>wF2ir4u;JA^{y5_d;55Pd=%@UDmgFU>2~w$ zOv#=jevqajZLMRO0tvu8;BEwUCCNXeHqtekXtGiFnl`rmacpG7x!|?Q+E?=> z<2B+0`as4Yvh)&mMZ4TtGpW}9+Kja?-HxA$B3h`GyzQtm-%)C(p=s3XFr$j5Wh;u= zp>2KdhZJRsov`Ad+vl{C{x$cZ)|EO$u|+i-0Q?RK^Nv;2{UJ51)+}&8ZIEm7y!&fR zN_>AcL2dD5;&W{CRvLo?ufNE1!KBIad9I1W=}f;atp$^!uCDuiHXfo_LoNEc>8b(E z0VTUE;-7O@R-cZ}3CZ!*1RZFTmGc^JG=m5F2Roy0B%IaZ_6SLxj@PMtW>mIa zT$KKq`jTpAVQ+uI%mj~PbUyPh9T-vCT{%GOXQ&MuIQwqBwHUgpt{R~NYw&mB7q6Z3 z((t+-e|68%I+*@sY7rAjBh3l+P@z{w=$U@S<&U3KkkP+>!e37H`RIGpQkc!QGg*0O zd37>C`?Q5OOf3bDjw&y8{Y`1DI+%f}DQe<>vyrX4^1S$HT*&2qiix8_xh)f=8s-2G zwaIvj%U|YiOKrPh2egF)-L#yP`SmU90hB82 zK9G4HA3xK$EIHSqdfa>&M~(LWT~GO{gK^P=q2Is|M~|Pft-d?BYmyfKW~$YCH+5e2 zTGQxi_TAX<-lRl}&d$Q*lGta0t$dsxrhuw*u2Hg4asngKa&Pjzne@v0)OYxxu2wS| zBxf~;>}`Fjgyqv!{D6+-W-ooOYZVJQyFvYG%G65yE!`lw{z|rYpo5ca9r)R(D!FlDrPtSsOiZrdXRiVo~we#@}naaIIgQd23Tw zygB}Tun+yFE>onnsRXmkwYYI6B`_rM8!NrdPOF*8{@UsvzTw{`S9gSKc$r*$X-EGI zTQ42AzGsxR>A1j+2cDWhlcfeHnshYB&b+?-a`VxvgeD%^#rtLYZ^ zq#7|J*R|u81Nc*UMbWkx+UzR|F0zB7*x7gK1*JhU^=FTSlxE6QhkE=f0_dX=65x6dOw5BMpeLDb|uHSe^L@RkxnlyBVt_h1i8}wiF zqhOCwa8@`jaz?)t7}PP9E1mQlT?Xmg9s}RVro<8)Q};Rr@#H(0myP94K~IQ&@NYnR zt-d`B*9BkPu6OVIykyphyN6S0sxw|)0?}XzXnFAKBt#LvaFdGOjHt|9J)N$u8e2glmGK(SBZOB7&-;8#SK!$hT{Y_s z3S%L1*jN$ruRO;!qSbwQk5;99sY&o5-{DNl6CYRbdHKIT@NF8SXLiVhrgXO7x~Ab!Az98SR6si_<8PZgsvzDkaUW!T_|Il1_G zI}?5SHyu0&<2WGHbBd8hItULXyvP(VANldu5QHA{yI#uB4I)>)US4kiN2*p*LAp-`p9pYrFpfsvzf+cep)t*27b)_*;x(pU(aHS zzy%F~D1gqcxxYt=-bU@ADlomW1k>X)N_)NIWgg|P3`7M>*Z0_h7yXIA;;ZxzShC!C zydJS(iEU!e?Q3a$Ne|K<9PRFN?)> z9i@B1kz`(A5F}qWtsIVHfn4WO_&^zVaj&@s_&{-%RL}B24L#h#M72NWBsc>|7;mqc z-u*y=U>=`#eKO`;k5WY?u6lveDIw2JM$ZbxEU>IN3~|o$8w?DY@$`%Ox?M0v3=D4r zkX^5TsYIeQzy~oRau2H#sJVX*)HbDDv7`%Sgg z7dFJ)4U5SX^j;?svg)kX+sM&kVwP-(K#n@I&aBQEBB?W`Zfc*7k??ktFsYgI3N$y} zgj!W4hbjsuYNT^wT>#p3fCw`^bUNZlMrT`pmbcI9ej5&dCh$kkHJijp;- zIQ0(o;L60F=!S^PO-7Z&atB2x_d1&MZ}-5zp0dkSM_r0_ah<~wJ~ z#t~)0AWnM`@A;f?A9WZpx{F4Po${LS6U|sC0IS!6CJ~ht$O@d+zjT@|EM)iN`RG!v za3&bJ?#&fZr|{u}PkCH!lbhAUnt@|9)l}Q()OYrhkEWaLeza{D?)kN)_fZt~RowU% z=HdEn6xKV5^;1D%#p?>&Gw7gYR!&e^vM%ZV_SKt;(7{ zHxtSbFKGEacq9B_tE4PYTJ~ke4FiiL1}iJDa8`Ye5$0QUQs7Rt>Ch+|Nw4$CD-#d;Jh%Xz?^VQlbTUA3$njn>Ch5yN#cB}! z=PQR`NkG^m!}2+f;~M461XpuS@xps9DM}2>^6Kifs%?C?%xGCI?;R{jB>TLPK&0X2 zOC0|`sr9FTZ;n?~qGozY#=teBpqaZM$#(P({JAXJjc;0*U#m%RcAVqavpD}q+0bDj zi@fT3vmS<(Xreb{r>}9&w^G5joSV=J~02bh?0H5>cbw@8KYWCxNXJC%4WsTd>?Ww(JHfif`n@{-Jxzc#fkGARR-M ze;VO>{DEJU>bkqM!a&He@AZOsWR?|p79k-;-l5!W3nnCHs%g{vhh?CWG+By&W@gXZ zysHL#;SMQU|Hf)!bladM)VCb|^Mrs3{v@-}CiSKB21DKFGWn_I*Q_Bn7#r+WC-{4UfT|)Ayvy6Qk$%R>p(Msn`|0NE zVFRFs>=XR>%Z`IX24@9pa#+lBY)jKy2P$-A?#Jh`cl?RT8u~IHS(>W)a0}PPp2Ykc zf&<46rRp{8oXs-a0O8*Nd;aJU-S`p~fSK+5wsj`OB1ae+jiUI3BeN1fA1mSbgd zJMVJIG7&CV>Ht_!nv4t^2A#BF-CQ;`n-)@9AQ2I83oAZFdr@D&+L+>H;{hi08z$5R z9YWyrHbJq3=%2tR078J$Kp3&fI=iIXyL=$Q^bau>R8pe%gN`9Z^t%$Oo&pLaVj?7B zps$FKy#a+m&)$NDc4+;_!>wx1nY&qOVkrsHhB-0e`FSa(oVtVCL(NAL=s6lhXyxeP zx1IU+>*<8Oi0^nrOV)`l76n8kjFDD1tMaPIus1Rjajjeq!*AYeM!f5>kHD#Fjq>;i zRU0kSY^4a_-Q7fnx?n&oPtD}a=jS@Zq&^qmI`7TceFJ5&Ac6NxqUB3K|4`Kuow)q9 z`-WRxA}U385!|G!efBbYZ6(xynZ!ddnMurr4hd{mu0l%$M4Aw?$YQ+o$WIuky`WrI zHp6hM?nzx(>l0w`ro;7u+;lEeXVn)4Batn^dD8)$rrHVeS9bvhoTOsZ?SH2-GOI#* z3GDd`L-DC;U`*A_TnfKyvU`D8@r9;Zayv{|YE>L{_J6SAfmjnuOuiESwkTkY7k>?+ zQ6W&&J;DG|GqeFr#BNg%2poV=A_7T65gdRFF^J(m1RxTEqxc_!3^IZf^&dhIDrP*8 z{67RA!x~3f?@pn%j*b@#E-yeb#IPoTc>2-J`ne~je{MCMPEpf}u}Trvfk(pr0zs(= z8Ia81HBag^^?%OX`LiWFS%XUP5T2UM{(%nX6Y2Lca7FwNa;<}y)C!*ZXikM(zhn5P zh}=xZ{y#*7ibV*L;r$OpEMg}%gwDlMpSp2ay9d|5Fkb>1CCe0{s93@o{~+08unA>f zjX1g!D1UKU7DNc-9ZYHn#OGv+ENdt1PsLgb1Zj6&Zt%!p<7a#cVX7{B_CTm5^K8rc zO_tTRtuYNux5>(pfMkjo!CTOk>K(VXz+(Tey+3sszyfauC+XVu5K6TK^M6iEk{-pOA z?{o<$!`t<<$cQ6=!#&8{r0WW);Y~3S%*#SgyyoHU@7Mp#7KInSPhQ1C^qfO{0O?Q5 zI;EgZCaf2;~p)c>5!Aw6Q9g#2wo9wP$ff62* zQJ+P5gJ6*Mwka^w2W=4!F${-C`y|Rh51j$VC6tFB2|=+ZtJ8~QMn^p9zy=)(v)Wou zG0jaaw;RSE&`6*w z9AYt~{m=Bq|DM+W5nx!P>)|v?iM=ji@7bKlwFs9xpcO9oHBcwZ(TX%&Rhr^iP?<4(&LEFW_{scF~sR?QOk0Y;J#&94(N=|0(vI^ zayMSFFNbz=tUkCy>##jAH%|cNpGmPjJQdbxHow+8ijN0xS`EmlA8W91J&?H!)2REX z>%F@s-z<6UdF+iO!oY6q@V~OL2E)aY)R%Xh?@E}tBRJo=vk?IoKaRlcfibi!1JQR7pdVI6k@((MJ;2 z(5sN?-T#mly2TCi??;e(D7o;AqHZtLlv@jHSft=&)qoF8e;wV;@gjb5$389S=~hEMuEQ}x}Ri3UF{_bu`;5=A91Cn&OhPD?#@%%<}CLEyZ)5GFW4@-1a5ThlLE(?|&wk~$Tc zLjPx{g1S=gJSc<&6bCi?Hq@;`#4DrOWV=bpN6iZYJKM+Su^5&Td8wwnDmVz@kF?N9 z9LCz5#?O{`AL0xxQ0$z-%+X{JP2#c9gjGYD;H8-b(iHLVIWe3a;h5J^a16CSFO?M# zJ751kZ}PZPis-}4YUMZwlq$Q@`Ze3Bco%pFVRLkPO;!BGBfetgk2dFm0>%)D{JrVf ztLZ5_^^*Xhz0B6xa&L++&_^Q_N)@0SP9%$5+@`V|R}1RYTkB(t2w&=N`=Ug|;Br>V zYn>0le*-f`LtN(kj`vp`jd~(y6U&;*7i(H4Y9zaBJv`h<{_H^`b8U!Kj_Xcx(NQuv?mng5_$mE^U-^SR!)JF9ZVi>lPv-eqG!o5q76GON-u5 z^plni;zYN8Hp}*R+JxVEza>%7@K&2`t^bwv&N@q&mpl-a!q>{Z%$I_{xjvs)Lke9% zgXF7K#;1u*Bw~6u+LUz1RME$69G7o@R1djiG)NTaf@WuaU?fOZ@<<;#5F=r2C}t74 z&hmYD<%boK!-7ptD4M~q7jU_v`u)1F>uq1l*5M;D!s*l%274#yjdlu&bi65bC0XUQ z%0D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 literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.png new file mode 100644 index 0000000000000000000000000000000000000000..c951d0c418b04f345c269307a278c156ca1a5c70 GIT binary patch literal 24529 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 literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.svg b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.svg new file mode 100644 index 0000000000..029b92fee4 --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/speech-to-text.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/suggested-questions-after-answer.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/suggested-questions-after-answer.png new file mode 100644 index 0000000000000000000000000000000000000000..bee4be0acf7a4f96252a39d845d0a8fc7e84ac03 GIT binary patch literal 42447 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%0DIHHARnZ~e<=P(Ej+YJgif};= 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%0DV?P)8sE z>BEjQXun|uokRcmO9Z4%NPE|-KW`^wo2oRgyQ+lWPa?ek!_GJF|M0)V_7_%W3E8&e z``_*}CHP$H<&OBg72djPeJ@75xXZ&|G%3FQ4wHcd@q(Z^U%~=^S_%3!r?f6`%=%i& zP(@!m$G zlP!I7kVPhV6mfmoAQ(yvOqkoqUe^4*5cO~4ziXTTuknIRM3l*|aKjX^t(HZ$s$%hN z>qxLVcLLT$9j()3c~IHI@{c;!pkoKskiT2mZ%}!qg|t-(?qI{}&fk$s$o4_s(jHfG@s&9!P1yaP2q{n0 zF_?XcY(3W^(e)dYSF_`cTGz_8)#~*Z^xMGuKYaUP-v4?ZJ_}3`4d={P;2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/preview-imgs/text-to-audio-preview-assistant@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..91396e72c75b34a9707de3d258d41f3d321f2508 GIT binary patch literal 23500 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

SDUaRr+>9aIL|AS=u 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 literal 0 HcmV?d00001 diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css new file mode 100644 index 0000000000..32dc6be3ba --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/style.module.css @@ -0,0 +1,41 @@ +.preview { + display: none; + position: absolute; + top: 0; + left: 100%; + transform: translate(32px, -54px); + width: 280px; + height: 360px; + background: center center no-repeat; + background-size: contain; + border-radius: 8px; +} + +.wrap:hover .preview { + display: block; +} + +.openingStatementPreview { + background-image: url(./preview-imgs/opening-statement.png); +} + +.suggestedQuestionsAfterAnswerPreview { + background-image: url(./preview-imgs/suggested-questions-after-answer.png); +} + +.moreLikeThisPreview { + background-image: url(./preview-imgs/more-like-this.png); +} + +.speechToTextPreview { + background-image: url(./preview-imgs/speech-to-text.png); +} + +.textToSpeechPreview { + @apply shadow-lg rounded-lg; + background-image: url(./preview-imgs/text-to-audio-preview-assistant@2x.png); +} + +.citationPreview { + background-image: url(./preview-imgs/citation.png); +} diff --git a/web/app/components/app/configuration/config/feature/choose-feature/index.tsx b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx new file mode 100644 index 0000000000..8364f9529d --- /dev/null +++ b/web/app/components/app/configuration/config/feature/choose-feature/index.tsx @@ -0,0 +1,172 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import FeatureGroup from '../feature-group' +import MoreLikeThisIcon from '../../../base/icons/more-like-this-icon' +import FeatureItem from './feature-item' +import Modal from '@/app/components/base/modal' +import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon' +import { Microphone01, Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import { Citations } from '@/app/components/base/icons/src/vender/solid/editor' +import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files' +import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' +type IConfig = { + openingStatement: boolean + moreLikeThis: boolean + suggestedQuestionsAfterAnswer: boolean + speechToText: boolean + textToSpeech: boolean + citation: boolean + moderation: boolean + annotation: boolean +} + +export type IChooseFeatureProps = { + isShow: boolean + onClose: () => void + config: IConfig + isChatApp: boolean + onChange: (key: string, value: boolean) => void + showTextToSpeechItem?: boolean + showSpeechToTextItem?: boolean +} + +const OpeningStatementIcon = ( + + + +) + +const ChooseFeature: FC = ({ + isShow, + onClose, + isChatApp, + config, + onChange, + showTextToSpeechItem, + showSpeechToTextItem, +}) => { + const { t } = useTranslation() + return ( + +

+ {/* Chat Feature */} + {isChatApp && ( + + <> + onChange('openingStatement', value)} + /> + } + previewImgClassName='suggestedQuestionsAfterAnswerPreview' + title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')} + description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')} + value={config.suggestedQuestionsAfterAnswer} + onChange={value => onChange('suggestedQuestionsAfterAnswer', value)} + /> + { + showTextToSpeechItem && ( + } + previewImgClassName='textToSpeechPreview' + title={t('appDebug.feature.textToSpeech.title')} + description={t('appDebug.feature.textToSpeech.description')} + value={config.textToSpeech} + onChange={value => onChange('textToSpeech', value)} + /> + ) + } + { + showSpeechToTextItem && ( + } + previewImgClassName='speechToTextPreview' + title={t('appDebug.feature.speechToText.title')} + description={t('appDebug.feature.speechToText.description')} + value={config.speechToText} + onChange={value => onChange('speechToText', value)} + /> + ) + } + } + previewImgClassName='citationPreview' + title={t('appDebug.feature.citation.title')} + description={t('appDebug.feature.citation.description')} + value={config.citation} + onChange={value => onChange('citation', value)} + /> + + + )} + + {/* Text Generation Feature */} + {!isChatApp && ( + + <> + } + previewImgClassName='moreLikeThisPreview' + title={t('appDebug.feature.moreLikeThis.title')} + description={t('appDebug.feature.moreLikeThis.description')} + value={config.moreLikeThis} + onChange={value => onChange('moreLikeThis', value)} + /> + { + showTextToSpeechItem && ( + } + previewImgClassName='textToSpeechPreview' + title={t('appDebug.feature.textToSpeech.title')} + description={t('appDebug.feature.textToSpeech.description')} + value={config.textToSpeech} + onChange={value => onChange('textToSpeech', value)} + /> + ) + } + + + )} + + <> + } + previewImgClassName='' + title={t('appDebug.feature.moderation.title')} + description={t('appDebug.feature.moderation.description')} + value={config.moderation} + onChange={value => onChange('moderation', value)} + /> + {isChatApp && ( + } + title={t('appDebug.feature.annotation.title')} + description={t('appDebug.feature.annotation.description')} + value={config.annotation} + onChange={value => onChange('annotation', value)} + /> + )} + + +
+ + ) +} +export default React.memo(ChooseFeature) diff --git a/web/app/components/app/configuration/config/feature/feature-group/index.tsx b/web/app/components/app/configuration/config/feature/feature-group/index.tsx new file mode 100644 index 0000000000..a4b27f18d4 --- /dev/null +++ b/web/app/components/app/configuration/config/feature/feature-group/index.tsx @@ -0,0 +1,31 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import GroupName from '@/app/components/app/configuration/base/group-name' + +export type IFeatureGroupProps = { + title: string + description?: string + children: React.ReactNode +} + +const FeatureGroup: FC = ({ + title, + description, + children, +}) => { + return ( +
+
+ + {description && ( +
{description}
+ )} +
+
+ {children} +
+
+ ) +} +export default React.memo(FeatureGroup) diff --git a/web/app/components/app/configuration/features/chat-group/citation/index.tsx b/web/app/components/app/configuration/features/chat-group/citation/index.tsx new file mode 100644 index 0000000000..4003b68cd3 --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/citation/index.tsx @@ -0,0 +1,25 @@ +'use client' +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import { Citations } from '@/app/components/base/icons/src/vender/solid/editor' + +const Citation: FC = () => { + const { t } = useTranslation() + + return ( + +
{t('appDebug.feature.citation.title')}
+ + } + headerIcon={} + headerRight={ +
{t('appDebug.feature.citation.resDes')}
+ } + noBodySpacing + /> + ) +} +export default React.memo(Citation) diff --git a/web/app/components/app/configuration/features/chat-group/index.tsx b/web/app/components/app/configuration/features/chat-group/index.tsx new file mode 100644 index 0000000000..fd3cfa3a68 --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/index.tsx @@ -0,0 +1,65 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import GroupName from '../../base/group-name' +import type { IOpeningStatementProps } from './opening-statement' +import OpeningStatement from './opening-statement' +import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer' +import SpeechToText from './speech-to-text' +import TextToSpeech from './text-to-speech' +import Citation from './citation' +/* +* Include +* 1. Conversation Opener +* 2. Opening Suggestion +* 3. Next question suggestion +*/ +type ChatGroupProps = { + isShowOpeningStatement: boolean + openingStatementConfig: IOpeningStatementProps + isShowSuggestedQuestionsAfterAnswer: boolean + isShowSpeechText: boolean + isShowTextToSpeech: boolean + isShowCitation: boolean +} +const ChatGroup: FC = ({ + isShowOpeningStatement, + openingStatementConfig, + isShowSuggestedQuestionsAfterAnswer, + isShowSpeechText, + isShowTextToSpeech, + isShowCitation, +}) => { + const { t } = useTranslation() + + return ( +
+ +
+ {isShowOpeningStatement && ( + + )} + {isShowSuggestedQuestionsAfterAnswer && ( + + )} + { + isShowTextToSpeech && ( + + ) + } + { + isShowSpeechText && ( + + ) + } + { + isShowCitation && ( + + ) + } +
+
+ ) +} +export default React.memo(ChatGroup) diff --git a/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx b/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx new file mode 100644 index 0000000000..e452b38971 --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/speech-to-text/index.tsx @@ -0,0 +1,25 @@ +'use client' +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' + +const SpeechToTextConfig: FC = () => { + const { t } = useTranslation() + + return ( + +
{t('appDebug.feature.speechToText.title')}
+ + } + headerIcon={} + headerRight={ +
{t('appDebug.feature.speechToText.resDes')}
+ } + noBodySpacing + /> + ) +} +export default React.memo(SpeechToTextConfig) diff --git a/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx b/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx new file mode 100644 index 0000000000..199558f4aa --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx @@ -0,0 +1,34 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon' +import Tooltip from '@/app/components/base/tooltip' + +const SuggestedQuestionsAfterAnswer: FC = () => { + const { t } = useTranslation() + + return ( + +
{t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
+ + {t('appDebug.feature.suggestedQuestionsAfterAnswer.description')} + + } + /> + + } + headerIcon={} + headerRight={ +
{t('appDebug.feature.suggestedQuestionsAfterAnswer.resDes')}
+ } + noBodySpacing + /> + ) +} +export default React.memo(SuggestedQuestionsAfterAnswer) diff --git a/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx new file mode 100644 index 0000000000..72d617c3c3 --- /dev/null +++ b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx @@ -0,0 +1,55 @@ +'use client' +import useSWR from 'swr' +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { usePathname } from 'next/navigation' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import ConfigContext from '@/context/debug-configuration' +import { languages } from '@/i18n/language' +import { fetchAppVoices } from '@/service/apps' +import AudioBtn from '@/app/components/base/audio-btn' + +const TextToSpeech: FC = () => { + const { t } = useTranslation() + const { + textToSpeechConfig, + } = useContext(ConfigContext) + + const pathname = usePathname() + const matched = pathname.match(/\/app\/([^/]+)/) + const appId = (matched?.length && matched[1]) ? matched[1] : '' + const language = textToSpeechConfig.language + const languageInfo = languages.find(i => i.value === textToSpeechConfig.language) + + const voiceItems = useSWR({ appId, language }, fetchAppVoices).data + const voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) + + return ( + +
{t('appDebug.feature.textToSpeech.title')}
+ + } + headerIcon={} + headerRight={ +
+ {languageInfo && (`${languageInfo?.name} - `)}{voiceItem?.name ?? t('appDebug.voice.defaultDisplay')} + { languageInfo?.example && ( + + )} +
+ } + noBodySpacing + isShowTextToSpeech + /> + ) +} +export default React.memo(TextToSpeech) diff --git a/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx new file mode 100644 index 0000000000..809b907d62 --- /dev/null +++ b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx @@ -0,0 +1,135 @@ +'use client' +import type { FC } from 'react' +import React, { useRef, useState } from 'react' +import { useHover } from 'ahooks' +import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' +import { MessageCheckRemove, MessageFastPlus } from '@/app/components/base/icons/src/vender/line/communication' +import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' +import { Edit04 } from '@/app/components/base/icons/src/vender/line/general' +import RemoveAnnotationConfirmModal from '@/app/components/app/annotation/remove-annotation-confirm-modal' +import Tooltip from '@/app/components/base/tooltip' +import { addAnnotation, delAnnotation } from '@/service/annotation' +import Toast from '@/app/components/base/toast' +import { useProviderContext } from '@/context/provider-context' +import { useModalContext } from '@/context/modal-context' + +type Props = { + appId: string + messageId?: string + annotationId?: string + className?: string + cached: boolean + query: string + answer: string + onAdded: (annotationId: string, authorName: string) => void + onEdit: () => void + onRemoved: () => void +} + +const CacheCtrlBtn: FC = ({ + className, + cached, + query, + answer, + appId, + messageId, + annotationId, + onAdded, + onEdit, + onRemoved, +}) => { + const { t } = useTranslation() + const { plan, enableBilling } = useProviderContext() + const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse) + const { setShowAnnotationFullModal } = useModalContext() + const [showModal, setShowModal] = useState(false) + const cachedBtnRef = useRef(null) + const isCachedBtnHovering = useHover(cachedBtnRef) + const handleAdd = async () => { + if (isAnnotationFull) { + setShowAnnotationFullModal() + return + } + const res: any = await addAnnotation(appId, { + message_id: messageId, + question: query, + answer, + }) + Toast.notify({ + message: t('common.api.actionSuccess') as string, + type: 'success', + }) + onAdded(res.id, res.account?.name) + } + + const handleRemove = async () => { + await delAnnotation(appId, annotationId!) + Toast.notify({ + message: t('common.api.actionSuccess') as string, + type: 'success', + }) + onRemoved() + setShowModal(false) + } + return ( +
+
+ {cached + ? ( +
+
setShowModal(true)} + > + {!isCachedBtnHovering + ? ( + <> + +
{t('appDebug.feature.annotation.cached')}
+ + ) + : <> + +
{t('appDebug.feature.annotation.remove')}
+ } +
+
+ ) + : answer + ? ( + +
+ +
+
+ ) + : null + } + +
+ +
+
+ +
+ setShowModal(false)} + onRemove={handleRemove} + /> +
+ ) +} +export default React.memo(CacheCtrlBtn) diff --git a/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx b/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx new file mode 100644 index 0000000000..b660977d08 --- /dev/null +++ b/web/app/components/app/configuration/toolbox/annotation/config-param-modal.tsx @@ -0,0 +1,139 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import ScoreSlider from '../score-slider' +import { Item } from './config-param' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import Toast from '@/app/components/base/toast' +import type { AnnotationReplyConfig } from '@/models/debug' +import { ANNOTATION_DEFAULT } from '@/config' +import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' +import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' + +type Props = { + appId: string + isShow: boolean + onHide: () => void + onSave: (embeddingModel: { + embedding_provider_name: string + embedding_model_name: string + }, score: number) => void + isInit?: boolean + annotationConfig: AnnotationReplyConfig +} + +const ConfigParamModal: FC = ({ + isShow, + onHide: doHide, + onSave, + isInit, + annotationConfig: oldAnnotationConfig, +}) => { + const { t } = useTranslation() + const { + modelList: embeddingsModelList, + defaultModel: embeddingsDefaultModel, + currentModel: isEmbeddingsDefaultModelValid, + } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textEmbedding) + const [annotationConfig, setAnnotationConfig] = useState(oldAnnotationConfig) + + const [isLoading, setLoading] = useState(false) + const [embeddingModel, setEmbeddingModel] = useState(oldAnnotationConfig.embedding_model + ? { + providerName: oldAnnotationConfig.embedding_model.embedding_provider_name, + modelName: oldAnnotationConfig.embedding_model.embedding_model_name, + } + : (embeddingsDefaultModel + ? { + providerName: embeddingsDefaultModel.provider.provider, + modelName: embeddingsDefaultModel.model, + } + : undefined)) + const onHide = () => { + if (!isLoading) + doHide() + } + + const handleSave = async () => { + if (!embeddingModel || !embeddingModel.modelName || (embeddingModel.modelName === embeddingsDefaultModel?.model && !isEmbeddingsDefaultModelValid)) { + Toast.notify({ + message: t('common.modelProvider.embeddingModel.required'), + type: 'error', + }) + return + } + setLoading(true) + await onSave({ + embedding_provider_name: embeddingModel.providerName, + embedding_model_name: embeddingModel.modelName, + }, annotationConfig.score_threshold) + setLoading(false) + } + + return ( + +
+ {t(`appAnnotation.initSetup.${isInit ? 'title' : 'configTitle'}`)} +
+ +
+ + { + setAnnotationConfig({ + ...annotationConfig, + score_threshold: val / 100, + }) + }} + /> + + + +
+ { + setEmbeddingModel({ + providerName: val.provider, + modelName: val.model, + }) + }} + /> +
+
+
+ +
+ + +
+
+ ) +} +export default React.memo(ConfigParamModal) diff --git a/web/app/components/app/configuration/toolbox/annotation/type.ts b/web/app/components/app/configuration/toolbox/annotation/type.ts new file mode 100644 index 0000000000..910453478c --- /dev/null +++ b/web/app/components/app/configuration/toolbox/annotation/type.ts @@ -0,0 +1,4 @@ +export enum PageType { + log = 'log', + annotation = 'annotation', +} diff --git a/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts b/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts new file mode 100644 index 0000000000..540302cb27 --- /dev/null +++ b/web/app/components/app/configuration/toolbox/annotation/use-annotation-config.ts @@ -0,0 +1,89 @@ +import React, { useState } from 'react' +import produce from 'immer' +import type { AnnotationReplyConfig } from '@/models/debug' +import { queryAnnotationJobStatus, updateAnnotationStatus } from '@/service/annotation' +import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type' +import { AnnotationEnableStatus, JobStatus } from '@/app/components/app/annotation/type' +import { sleep } from '@/utils' +import { ANNOTATION_DEFAULT } from '@/config' +import { useProviderContext } from '@/context/provider-context' + +type Params = { + appId: string + annotationConfig: AnnotationReplyConfig + setAnnotationConfig: (annotationConfig: AnnotationReplyConfig) => void +} +const useAnnotationConfig = ({ + appId, + annotationConfig, + setAnnotationConfig, +}: Params) => { + const { plan, enableBilling } = useProviderContext() + const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse) + const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false) + const [isShowAnnotationConfigInit, doSetIsShowAnnotationConfigInit] = React.useState(false) + const setIsShowAnnotationConfigInit = (isShow: boolean) => { + if (isShow) { + if (isAnnotationFull) { + setIsShowAnnotationFullModal(true) + return + } + } + doSetIsShowAnnotationConfigInit(isShow) + } + const ensureJobCompleted = async (jobId: string, status: AnnotationEnableStatus) => { + let isCompleted = false + while (!isCompleted) { + const res: any = await queryAnnotationJobStatus(appId, status, jobId) + isCompleted = res.job_status === JobStatus.completed + if (isCompleted) + break + + await sleep(2000) + } + } + + const handleEnableAnnotation = async (embeddingModel: EmbeddingModelConfig, score?: number) => { + if (isAnnotationFull) + return + + const { job_id: jobId }: any = await updateAnnotationStatus(appId, AnnotationEnableStatus.enable, embeddingModel, score) + await ensureJobCompleted(jobId, AnnotationEnableStatus.enable) + setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => { + draft.enabled = true + draft.embedding_model = embeddingModel + if (!draft.score_threshold) + draft.score_threshold = ANNOTATION_DEFAULT.score_threshold + })) + } + + const setScore = (score: number, embeddingModel?: EmbeddingModelConfig) => { + setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => { + draft.score_threshold = score + if (embeddingModel) + draft.embedding_model = embeddingModel + })) + } + + const handleDisableAnnotation = async (embeddingModel: EmbeddingModelConfig) => { + if (!annotationConfig.enabled) + return + + await updateAnnotationStatus(appId, AnnotationEnableStatus.disable, embeddingModel) + setAnnotationConfig(produce(annotationConfig, (draft: AnnotationReplyConfig) => { + draft.enabled = false + })) + } + + return { + handleEnableAnnotation, + handleDisableAnnotation, + isShowAnnotationConfigInit, + setIsShowAnnotationConfigInit, + isShowAnnotationFullModal, + setIsShowAnnotationFullModal, + setScore, + } +} + +export default useAnnotationConfig diff --git a/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx b/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx new file mode 100644 index 0000000000..daf964447b --- /dev/null +++ b/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx @@ -0,0 +1,79 @@ +import type { FC } from 'react' +import { useContext } from 'use-context-selector' +import type { CodeBasedExtensionForm } from '@/models/common' +import I18n from '@/context/i18n' +import { PortalSelect } from '@/app/components/base/select' +import type { ModerationConfig } from '@/models/debug' + +type FormGenerationProps = { + forms: CodeBasedExtensionForm[] + value: ModerationConfig['config'] + onChange: (v: Record) => void +} +const FormGeneration: FC = ({ + forms, + value, + onChange, +}) => { + const { locale } = useContext(I18n) + + const handleFormChange = (type: string, v: string) => { + onChange({ ...value, [type]: v }) + } + + return ( + <> + { + forms.map((form, index) => ( +
+
+ {locale === 'zh-Hans' ? form.label['zh-Hans'] : form.label['en-US']} +
+ { + form.type === 'text-input' && ( + handleFormChange(form.variable, e.target.value)} + /> + ) + } + { + form.type === 'paragraph' && ( +
+ +
+ ) + : ( +
+ )} + {renderQuestions()} + ) : ( +
{t('appDebug.openingStatement.noDataPlaceHolder')}
+ )} + + {isShowConfirmAddVar && ( + + )} + +
+
+ ) +} +export default React.memo(OpeningStatement) diff --git a/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx b/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx new file mode 100644 index 0000000000..2e08a99122 --- /dev/null +++ b/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx @@ -0,0 +1,38 @@ +import ReactSlider from 'react-slider' +import s from './style.module.css' +import cn from '@/utils/classnames' + +type ISliderProps = { + className?: string + value: number + max?: number + min?: number + step?: number + disabled?: boolean + onChange: (value: number) => void +} + +const Slider: React.FC = ({ className, max, min, step, value, disabled, onChange }) => { + return ( +
+
+
+ {(state.valueNow / 100).toFixed(2)} +
+
+
+ )} + /> +} + +export default Slider diff --git a/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css b/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css new file mode 100644 index 0000000000..4e93b39563 --- /dev/null +++ b/web/app/components/base/features/feature-panel/score-slider/base-slider/style.module.css @@ -0,0 +1,20 @@ +.slider { + position: relative; +} + +.slider.disabled { + opacity: 0.6; +} + +.slider-thumb:focus { + outline: none; +} + +.slider-track { + background-color: #528BFF; + height: 2px; +} + +.slider-track-1 { + background-color: #E5E7EB; +} \ No newline at end of file diff --git a/web/app/components/base/features/feature-panel/score-slider/index.tsx b/web/app/components/base/features/feature-panel/score-slider/index.tsx new file mode 100644 index 0000000000..9826cbadcf --- /dev/null +++ b/web/app/components/base/features/feature-panel/score-slider/index.tsx @@ -0,0 +1,46 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Slider from '@/app/components/app/configuration/toolbox/score-slider/base-slider' + +type Props = { + className?: string + value: number + onChange: (value: number) => void +} + +const ScoreSlider: FC = ({ + className, + value, + onChange, +}) => { + const { t } = useTranslation() + + return ( +
+
+ +
+
+
+
0.8
+
·
+
{t('appDebug.feature.annotation.scoreThreshold.easyMatch')}
+
+
+
1.0
+
·
+
{t('appDebug.feature.annotation.scoreThreshold.accurateMatch')}
+
+
+
+ ) +} +export default React.memo(ScoreSlider) diff --git a/web/app/components/base/features/feature-panel/speech-to-text/index.tsx b/web/app/components/base/features/feature-panel/speech-to-text/index.tsx new file mode 100644 index 0000000000..2e5e3de439 --- /dev/null +++ b/web/app/components/base/features/feature-panel/speech-to-text/index.tsx @@ -0,0 +1,22 @@ +'use client' +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' + +const SpeechToTextConfig: FC = () => { + const { t } = useTranslation() + + return ( +
+
+ +
+
+
{t('appDebug.feature.speechToText.title')}
+
+
+
{t('appDebug.feature.speechToText.resDes')}
+
+ ) +} +export default React.memo(SpeechToTextConfig) diff --git a/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx b/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx new file mode 100644 index 0000000000..e6d0b6e7e0 --- /dev/null +++ b/web/app/components/base/features/feature-panel/suggested-questions-after-answer/index.tsx @@ -0,0 +1,25 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { MessageSmileSquare } from '@/app/components/base/icons/src/vender/solid/communication' +import Tooltip from '@/app/components/base/tooltip' + +const SuggestedQuestionsAfterAnswer: FC = () => { + const { t } = useTranslation() + + return ( +
+
+ +
+
+
{t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
+ +
+
+
{t('appDebug.feature.suggestedQuestionsAfterAnswer.resDes')}
+
+ ) +} +export default React.memo(SuggestedQuestionsAfterAnswer) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/index.tsx b/web/app/components/base/features/feature-panel/text-to-speech/index.tsx new file mode 100644 index 0000000000..2480a19077 --- /dev/null +++ b/web/app/components/base/features/feature-panel/text-to-speech/index.tsx @@ -0,0 +1,62 @@ +'use client' +import useSWR from 'swr' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { usePathname } from 'next/navigation' +import { useFeatures } from '../../hooks' +import type { OnFeaturesChange } from '../../types' +import ParamsConfig from './params-config' +import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import { languages } from '@/i18n/language' +import { fetchAppVoices } from '@/service/apps' +import AudioBtn from '@/app/components/base/audio-btn' + +type TextToSpeechProps = { + onChange?: OnFeaturesChange + disabled?: boolean +} +const TextToSpeech = ({ + onChange, + disabled, +}: TextToSpeechProps) => { + const { t } = useTranslation() + const textToSpeech = useFeatures(s => s.features.text2speech) + + const pathname = usePathname() + const matched = pathname.match(/\/app\/([^/]+)/) + const appId = (matched?.length && matched[1]) ? matched[1] : '' + const language = textToSpeech?.language + const languageInfo = languages.find(i => i.value === textToSpeech?.language) + + const voiceItems = useSWR({ appId, language }, fetchAppVoices).data + const voiceItem = voiceItems?.find(item => item.value === textToSpeech?.voice) + + return ( +
+
+ +
+
+ {t('appDebug.feature.textToSpeech.title')} +
+
+
+
+ {languageInfo && (`${languageInfo?.name} - `)}{voiceItem?.name ?? t('appDebug.voice.defaultDisplay')} + { languageInfo?.example && ( + + )} +
+
+ +
+
+ ) +} +export default React.memo(TextToSpeech) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx b/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx new file mode 100644 index 0000000000..e923d9a333 --- /dev/null +++ b/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx @@ -0,0 +1,241 @@ +'use client' +import useSWR from 'swr' +import produce from 'immer' +import React, { Fragment } from 'react' +import { usePathname } from 'next/navigation' +import { useTranslation } from 'react-i18next' +import { Listbox, Transition } from '@headlessui/react' +import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' +import { + useFeatures, + useFeaturesStore, +} from '../../hooks' +import type { OnFeaturesChange } from '../../types' +import classNames from '@/utils/classnames' +import type { Item } from '@/app/components/base/select' +import { fetchAppVoices } from '@/service/apps' +import Tooltip from '@/app/components/base/tooltip' +import { languages } from '@/i18n/language' +import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group' +import { TtsAutoPlay } from '@/types/app' + +type VoiceParamConfigProps = { + onChange?: OnFeaturesChange +} +const VoiceParamConfig = ({ + onChange, +}: VoiceParamConfigProps) => { + const { t } = useTranslation() + const pathname = usePathname() + const matched = pathname.match(/\/app\/([^/]+)/) + const appId = (matched?.length && matched[1]) ? matched[1] : '' + const text2speech = useFeatures(state => state.features.text2speech) + const featuresStore = useFeaturesStore() + + let languageItem = languages.find(item => item.value === text2speech?.language) + if (languages && !languageItem) + languageItem = languages[0] + const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') + + const language = languageItem?.value + const voiceItems = useSWR({ appId, language }, fetchAppVoices).data + let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice) + if (voiceItems && !voiceItem) + voiceItem = voiceItems[0] + const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') + + const handleChange = (value: Record) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + draft.text2speech = { + ...draft.text2speech, + ...value, + } + }) + + setFeatures(newFeatures) + if (onChange) + onChange(newFeatures) + } + + return ( +
+
+
{t('appDebug.voice.voiceSettings.title')}
+
+
+
+
{t('appDebug.voice.voiceSettings.language')}
+ + {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => ( +
{item} +
+ ))} +
+ } + /> +
+ { + handleChange({ + language: String(value.value), + }) + }} + > +
+ + + {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} + + + + + + + + {languages.map((item: Item) => ( + + `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' + }` + } + value={item} + disabled={false} + > + {({ /* active, */ selected }) => ( + <> + {t(`common.voice.language.${(item.value).toString().replace('-', '')}`)} + {(selected || item.value === text2speech?.language) && ( + + + )} + + )} + + ))} + + +
+
+
+ +
+
{t('appDebug.voice.voiceSettings.voice')}
+ { + handleChange({ + voice: String(value.value), + }) + }} + > +
+ + {voiceItem?.name ?? localVoicePlaceholder} + + + + + + + {voiceItems?.map((item: Item) => ( + + `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : '' + }` + } + value={item} + disabled={false} + > + {({ /* active, */ selected }) => ( + <> + {item.name} + {(selected || item.value === text2speech?.voice) && ( + + + )} + + )} + + ))} + + +
+
+
+
+
{t('appDebug.voice.voiceSettings.autoPlay')}
+ { + handleChange({ + autoPlay: value, + }) + }} + /> +
+
+
+ + ) +} + +export default React.memo(VoiceParamConfig) diff --git a/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx b/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx new file mode 100644 index 0000000000..095fd6cce8 --- /dev/null +++ b/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx @@ -0,0 +1,48 @@ +'use client' +import { memo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import type { OnFeaturesChange } from '../../types' +import ParamConfigContent from './param-config-content' +import cn from '@/utils/classnames' +import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +type ParamsConfigProps = { + onChange?: OnFeaturesChange + disabled?: boolean +} +const ParamsConfig = ({ + onChange, + disabled, +}: ParamsConfigProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + return ( + + !disabled && setOpen(v => !v)}> +
+ +
{t('appDebug.voice.settings')}
+
+
+ +
+ +
+
+
+ ) +} +export default memo(ParamsConfig) diff --git a/web/app/signin/forms.tsx b/web/app/signin/forms.tsx new file mode 100644 index 0000000000..70a34c26fa --- /dev/null +++ b/web/app/signin/forms.tsx @@ -0,0 +1,34 @@ +'use client' +import React from 'react' +import { useSearchParams } from 'next/navigation' + +import NormalForm from './normalForm' +import OneMoreStep from './oneMoreStep' +import cn from '@/utils/classnames' + +const Forms = () => { + const searchParams = useSearchParams() + const step = searchParams.get('step') + + const getForm = () => { + switch (step) { + case 'next': + return + default: + return + } + } + return
+
+ {getForm()} +
+
+} + +export default Forms diff --git a/web/app/signin/userSSOForm.tsx b/web/app/signin/userSSOForm.tsx new file mode 100644 index 0000000000..f01afa9eaf --- /dev/null +++ b/web/app/signin/userSSOForm.tsx @@ -0,0 +1,107 @@ +'use client' +import { useRouter, useSearchParams } from 'next/navigation' +import type { FC } from 'react' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' +import Toast from '@/app/components/base/toast' +import { getUserOAuth2SSOUrl, getUserOIDCSSOUrl, getUserSAMLSSOUrl } from '@/service/sso' +import Button from '@/app/components/base/button' +import useRefreshToken from '@/hooks/use-refresh-token' + +type UserSSOFormProps = { + protocol: string +} + +const UserSSOForm: FC = ({ + protocol, +}) => { + const { getNewAccessToken } = useRefreshToken() + const searchParams = useSearchParams() + const consoleToken = searchParams.get('access_token') + const refreshToken = searchParams.get('refresh_token') + const message = searchParams.get('message') + + const router = useRouter() + const { t } = useTranslation() + + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + if (refreshToken && consoleToken) { + localStorage.setItem('console_token', consoleToken) + localStorage.setItem('refresh_token', refreshToken) + getNewAccessToken() + router.replace('/apps') + } + + if (message) { + Toast.notify({ + type: 'error', + message, + }) + } + }, [consoleToken, refreshToken, message, router]) + + const handleSSOLogin = () => { + setIsLoading(true) + if (protocol === 'saml') { + getUserSAMLSSOUrl().then((res) => { + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else if (protocol === 'oidc') { + getUserOIDCSSOUrl().then((res) => { + document.cookie = `user-oidc-state=${res.state}` + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else if (protocol === 'oauth2') { + getUserOAuth2SSOUrl().then((res) => { + document.cookie = `user-oauth2-state=${res.state}` + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else { + Toast.notify({ + type: 'error', + message: 'invalid SSO protocol', + }) + setIsLoading(false) + } + } + + return ( +
+
+
+

{t('login.pageTitle')}

+
+
+ +
+
+
+ ) +} + +export default UserSSOForm