From f019bc4bd70d10e00f88173869f196202361b492 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 23 Jul 2024 16:22:06 +0800 Subject: [PATCH 1/8] feat(variables): Support `to_object`. (#6572) --- api/core/app/segments/segments.py | 9 +++++ api/core/workflow/entities/variable_pool.py | 11 +---- api/tests/unit_tests/app/test_variables.py | 45 +++++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/api/core/app/segments/segments.py b/api/core/app/segments/segments.py index a6e953829e..b7ca250ff2 100644 --- a/api/core/app/segments/segments.py +++ b/api/core/app/segments/segments.py @@ -33,6 +33,15 @@ class Segment(BaseModel): def markdown(self) -> str: return str(self.value) + def to_object(self) -> Any: + if isinstance(self.value, Segment): + return self.value.to_object() + if isinstance(self.value, list): + return [v.to_object() for v in self.value] + if isinstance(self.value, dict): + return {k: v.to_object() for k, v in self.value.items()} + return self.value + class StringSegment(Segment): value_type: SegmentType = SegmentType.STRING diff --git a/api/core/workflow/entities/variable_pool.py b/api/core/workflow/entities/variable_pool.py index 23076d5ca4..480f5cce92 100644 --- a/api/core/workflow/entities/variable_pool.py +++ b/api/core/workflow/entities/variable_pool.py @@ -4,7 +4,7 @@ from typing import Any, Union from typing_extensions import deprecated -from core.app.segments import ArrayVariable, ObjectVariable, Variable, factory +from core.app.segments import Variable, factory from core.file.file_obj import FileVar from core.workflow.entities.node_entities import SystemVariable @@ -113,14 +113,7 @@ class VariablePool: raise ValueError('Invalid selector') hash_key = hash(tuple(selector[1:])) value = self._variable_dictionary[selector[0]].get(hash_key) - - if value is None: - return value - if isinstance(value, ArrayVariable): - return [element.value for element in value.value] - if isinstance(value, ObjectVariable): - return {k: v.value for k, v in value.value.items()} - return value.value if value else None + return value.to_object() if value else None def remove(self, selector: Sequence[str], /): """ diff --git a/api/tests/unit_tests/app/test_variables.py b/api/tests/unit_tests/app/test_variables.py index 65db88a4a8..05b080bdcf 100644 --- a/api/tests/unit_tests/app/test_variables.py +++ b/api/tests/unit_tests/app/test_variables.py @@ -9,6 +9,7 @@ from core.app.segments import ( StringVariable, factory, ) +from core.app.segments.variables import ArrayVariable, ObjectVariable def test_string_variable(): @@ -89,3 +90,47 @@ def test_build_a_blank_string(): ) assert isinstance(result, StringVariable) assert result.value == '' + + +def test_object_variable_to_object(): + var = ObjectVariable( + name='object', + value={ + 'key1': ObjectVariable( + name='object', + value={ + 'key2': StringVariable(name='key2', value='value2'), + }, + ), + 'key2': ArrayVariable( + name='array', + value=[ + StringVariable(name='key5_1', value='value5_1'), + IntegerVariable(name='key5_2', value=42), + ObjectVariable(name='key5_3', value={}), + ], + ), + }, + ) + + assert var.to_object() == { + 'key1': { + 'key2': 'value2', + }, + 'key2': [ + 'value5_1', + 42, + {}, + ], + } + + +def test_variable_to_object(): + var = StringVariable(name='text', value='text') + assert var.to_object() == 'text' + var = IntegerVariable(name='integer', value=42) + assert var.to_object() == 42 + var = FloatVariable(name='float', value=3.14) + assert var.to_object() == 3.14 + var = SecretVariable(name='secret', value='secret_value') + assert var.to_object() == 'secret_value' From f17d4fe412ca3efaefd26a8d2634ac149fc94353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 23 Jul 2024 16:32:36 +0800 Subject: [PATCH 2/8] fix: extract only `like` feedback to caculate User Satisfaction (#6553) --- api/controllers/console/app/statistic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index d687b52dc8..b882ffef34 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -281,7 +281,7 @@ class UserSatisfactionRateStatistic(Resource): SELECT date(DATE_TRUNC('day', m.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, COUNT(m.id) as message_count, COUNT(mf.id) as feedback_count FROM messages m - LEFT JOIN message_feedbacks mf on mf.message_id=m.id + LEFT JOIN message_feedbacks mf on mf.message_id=m.id and mf.rating='like' WHERE m.app_id = :app_id ''' arg_dict = {'tz': account.timezone, 'app_id': app_model.id} From 7c55c390859883410b10381f6d106d8c0e0faab3 Mon Sep 17 00:00:00 2001 From: Lance Mao Date: Tue, 23 Jul 2024 16:38:39 +0800 Subject: [PATCH 3/8] feat: add tencent asr (#6091) --- .../model_providers/_position.yaml | 1 + .../model_providers/tencent/__init__.py | 0 .../tencent/_assets/icon_l_en.svg | 13 ++ .../tencent/_assets/icon_l_zh.svg | 13 ++ .../tencent/_assets/icon_s_en.svg | 11 ++ .../tencent/speech2text/__init__.py | 0 .../tencent/speech2text/flash_recognizer.py | 156 ++++++++++++++++++ .../tencent/speech2text/speech2text.py | 92 +++++++++++ .../tencent/speech2text/tencent.yaml | 5 + .../model_providers/tencent/tencent.py | 29 ++++ .../model_providers/tencent/tencent.yaml | 49 ++++++ 11 files changed, 369 insertions(+) create mode 100644 api/core/model_runtime/model_providers/tencent/__init__.py create mode 100644 api/core/model_runtime/model_providers/tencent/_assets/icon_l_en.svg create mode 100644 api/core/model_runtime/model_providers/tencent/_assets/icon_l_zh.svg create mode 100644 api/core/model_runtime/model_providers/tencent/_assets/icon_s_en.svg create mode 100644 api/core/model_runtime/model_providers/tencent/speech2text/__init__.py create mode 100644 api/core/model_runtime/model_providers/tencent/speech2text/flash_recognizer.py create mode 100644 api/core/model_runtime/model_providers/tencent/speech2text/speech2text.py create mode 100644 api/core/model_runtime/model_providers/tencent/speech2text/tencent.yaml create mode 100644 api/core/model_runtime/model_providers/tencent/tencent.py create mode 100644 api/core/model_runtime/model_providers/tencent/tencent.yaml diff --git a/api/core/model_runtime/model_providers/_position.yaml b/api/core/model_runtime/model_providers/_position.yaml index cf4ac10828..c2fa0e5a6e 100644 --- a/api/core/model_runtime/model_providers/_position.yaml +++ b/api/core/model_runtime/model_providers/_position.yaml @@ -23,6 +23,7 @@ - tongyi - wenxin - moonshot +- tencent - jina - chatglm - yi diff --git a/api/core/model_runtime/model_providers/tencent/__init__.py b/api/core/model_runtime/model_providers/tencent/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/tencent/_assets/icon_l_en.svg b/api/core/model_runtime/model_providers/tencent/_assets/icon_l_en.svg new file mode 100644 index 0000000000..63c7c8f988 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/_assets/icon_l_en.svg @@ -0,0 +1,13 @@ + + + + + tencent-cloud + + + \ No newline at end of file diff --git a/api/core/model_runtime/model_providers/tencent/_assets/icon_l_zh.svg b/api/core/model_runtime/model_providers/tencent/_assets/icon_l_zh.svg new file mode 100644 index 0000000000..63c7c8f988 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/_assets/icon_l_zh.svg @@ -0,0 +1,13 @@ + + + + + tencent-cloud + + + \ No newline at end of file diff --git a/api/core/model_runtime/model_providers/tencent/_assets/icon_s_en.svg b/api/core/model_runtime/model_providers/tencent/_assets/icon_s_en.svg new file mode 100644 index 0000000000..a3299b9201 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/_assets/icon_s_en.svg @@ -0,0 +1,11 @@ + + + + + tencent-cloud + + \ No newline at end of file diff --git a/api/core/model_runtime/model_providers/tencent/speech2text/__init__.py b/api/core/model_runtime/model_providers/tencent/speech2text/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/tencent/speech2text/flash_recognizer.py b/api/core/model_runtime/model_providers/tencent/speech2text/flash_recognizer.py new file mode 100644 index 0000000000..c3e3b7c258 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/speech2text/flash_recognizer.py @@ -0,0 +1,156 @@ +import base64 +import hashlib +import hmac +import time + +import requests + + +class Credential: + def __init__(self, secret_id, secret_key): + self.secret_id = secret_id + self.secret_key = secret_key + + +class FlashRecognitionRequest: + def __init__(self, voice_format="mp3", engine_type="16k_zh"): + self.engine_type = engine_type + self.speaker_diarization = 0 + self.hotword_id = "" + self.customization_id = "" + self.filter_dirty = 0 + self.filter_modal = 0 + self.filter_punc = 0 + self.convert_num_mode = 1 + self.word_info = 0 + self.voice_format = voice_format + self.first_channel_only = 1 + self.reinforce_hotword = 0 + self.sentence_max_length = 0 + + def set_first_channel_only(self, first_channel_only): + self.first_channel_only = first_channel_only + + def set_speaker_diarization(self, speaker_diarization): + self.speaker_diarization = speaker_diarization + + def set_filter_dirty(self, filter_dirty): + self.filter_dirty = filter_dirty + + def set_filter_modal(self, filter_modal): + self.filter_modal = filter_modal + + def set_filter_punc(self, filter_punc): + self.filter_punc = filter_punc + + def set_convert_num_mode(self, convert_num_mode): + self.convert_num_mode = convert_num_mode + + def set_word_info(self, word_info): + self.word_info = word_info + + def set_hotword_id(self, hotword_id): + self.hotword_id = hotword_id + + def set_customization_id(self, customization_id): + self.customization_id = customization_id + + def set_voice_format(self, voice_format): + self.voice_format = voice_format + + def set_sentence_max_length(self, sentence_max_length): + self.sentence_max_length = sentence_max_length + + def set_reinforce_hotword(self, reinforce_hotword): + self.reinforce_hotword = reinforce_hotword + + +class FlashRecognizer: + """ + reponse: + request_id string + status Integer + message String + audio_duration Integer + flash_result Result Array + + Result: + text String + channel_id Integer + sentence_list Sentence Array + + Sentence: + text String + start_time Integer + end_time Integer + speaker_id Integer + word_list Word Array + + Word: + word String + start_time Integer + end_time Integer + stable_flag: Integer + """ + + def __init__(self, appid, credential): + self.credential = credential + self.appid = appid + + def _format_sign_string(self, param): + signstr = "POSTasr.cloud.tencent.com/asr/flash/v1/" + for t in param: + if 'appid' in t: + signstr += str(t[1]) + break + signstr += "?" + for x in param: + tmp = x + if 'appid' in x: + continue + for t in tmp: + signstr += str(t) + signstr += "=" + signstr = signstr[:-1] + signstr += "&" + signstr = signstr[:-1] + return signstr + + def _build_header(self): + header = {"Host": "asr.cloud.tencent.com"} + return header + + def _sign(self, signstr, secret_key): + hmacstr = hmac.new(secret_key.encode('utf-8'), + signstr.encode('utf-8'), hashlib.sha1).digest() + s = base64.b64encode(hmacstr) + s = s.decode('utf-8') + return s + + def _build_req_with_signature(self, secret_key, params, header): + query = sorted(params.items(), key=lambda d: d[0]) + signstr = self._format_sign_string(query) + signature = self._sign(signstr, secret_key) + header["Authorization"] = signature + requrl = "https://" + requrl += signstr[4::] + return requrl + + def _create_query_arr(self, req): + return { + 'appid': self.appid, 'secretid': self.credential.secret_id, 'timestamp': str(int(time.time())), + 'engine_type': req.engine_type, 'voice_format': req.voice_format, + 'speaker_diarization': req.speaker_diarization, 'hotword_id': req.hotword_id, + 'customization_id': req.customization_id, 'filter_dirty': req.filter_dirty, + 'filter_modal': req.filter_modal, 'filter_punc': req.filter_punc, + 'convert_num_mode': req.convert_num_mode, 'word_info': req.word_info, + 'first_channel_only': req.first_channel_only, 'reinforce_hotword': req.reinforce_hotword, + 'sentence_max_length': req.sentence_max_length + } + + def recognize(self, req, data): + header = self._build_header() + query_arr = self._create_query_arr(req) + req_url = self._build_req_with_signature(self.credential.secret_key, query_arr, header) + r = requests.post(req_url, headers=header, data=data) + return r.text diff --git a/api/core/model_runtime/model_providers/tencent/speech2text/speech2text.py b/api/core/model_runtime/model_providers/tencent/speech2text/speech2text.py new file mode 100644 index 0000000000..00ec5aa9c8 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/speech2text/speech2text.py @@ -0,0 +1,92 @@ +import json +from typing import IO, Optional + +import requests + +from core.model_runtime.errors.invoke import ( + InvokeAuthorizationError, + InvokeConnectionError, + InvokeError, +) +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.speech2text_model import Speech2TextModel +from core.model_runtime.model_providers.tencent.speech2text.flash_recognizer import ( + Credential, + FlashRecognitionRequest, + FlashRecognizer, +) + + +class TencentSpeech2TextModel(Speech2TextModel): + def _invoke(self, model: str, credentials: dict, + file: IO[bytes], user: Optional[str] = None) \ + -> str: + """ + Invoke speech2text model + + :param model: model name + :param credentials: model credentials + :param file: audio file + :param user: unique user id + :return: text for given audio file + """ + return self._speech2text_invoke(model, credentials, file) + + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + try: + audio_file_path = self._get_demo_file_path() + + with open(audio_file_path, 'rb') as audio_file: + self._speech2text_invoke(model, credentials, audio_file) + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) + + def _speech2text_invoke(self, model: str, credentials: dict, file: IO[bytes]) -> str: + """ + Invoke speech2text model + + :param model: model name + :param credentials: model credentials + :param file: audio file + :return: text for given audio file + """ + app_id = credentials["app_id"] + secret_id = credentials["secret_id"] + secret_key = credentials["secret_key"] + voice_format = file.voice_format if hasattr(file, "voice_format") else "mp3" + tencent_voice_recognizer = FlashRecognizer(app_id, Credential(secret_id, secret_key)) + resp = tencent_voice_recognizer.recognize(FlashRecognitionRequest(voice_format), file) + resp = json.loads(resp) + code = resp["code"] + message = resp["message"] + if code == 4002: + raise CredentialsValidateFailedError(str(message)) + elif code != 0: + return f"Tencent ASR Recognition failed with code {code} and message {message}" + return "\n".join(item["text"] for item in resp["flash_result"]) + + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + Map model invoke error to unified error + The key is the error type thrown to the caller + The value is the error type thrown by the model, + which needs to be converted into a unified error type for the caller. + + :return: Invoke error mapping + """ + return { + InvokeConnectionError: [ + requests.exceptions.ConnectionError + ], + InvokeAuthorizationError: [ + CredentialsValidateFailedError + ] + } diff --git a/api/core/model_runtime/model_providers/tencent/speech2text/tencent.yaml b/api/core/model_runtime/model_providers/tencent/speech2text/tencent.yaml new file mode 100644 index 0000000000..618d19ac7c --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/speech2text/tencent.yaml @@ -0,0 +1,5 @@ +model: tencent +model_type: speech2text +model_properties: + file_upload_limit: 25 + supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm diff --git a/api/core/model_runtime/model_providers/tencent/tencent.py b/api/core/model_runtime/model_providers/tencent/tencent.py new file mode 100644 index 0000000000..dd9f90bb47 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/tencent.py @@ -0,0 +1,29 @@ +import logging + +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.model_provider import ModelProvider + +logger = logging.getLogger(__name__) + + +class TencentProvider(ModelProvider): + def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ + try: + model_instance = self.get_model_instance(ModelType.SPEECH2TEXT) + model_instance.validate_credentials( + model='tencent', + credentials=credentials + ) + except CredentialsValidateFailedError as ex: + raise ex + except Exception as ex: + logger.exception(f'{self.get_provider_schema().provider} credentials validate failed') + raise ex diff --git a/api/core/model_runtime/model_providers/tencent/tencent.yaml b/api/core/model_runtime/model_providers/tencent/tencent.yaml new file mode 100644 index 0000000000..7d8d5a1866 --- /dev/null +++ b/api/core/model_runtime/model_providers/tencent/tencent.yaml @@ -0,0 +1,49 @@ +provider: tencent +label: + zh_Hans: 腾讯云 + en_US: Tencent +icon_small: + en_US: icon_s_en.svg +icon_large: + zh_Hans: icon_l_zh.svg + en_US: icon_l_en.svg +background: "#E5E7EB" +help: + title: + en_US: Get your API key from Tencent AI + zh_Hans: 从腾讯云获取 API Key + url: + en_US: https://cloud.tencent.com/product/asr +supported_model_types: + - speech2text +configurate_methods: + - predefined-model +provider_credential_schema: + credential_form_schemas: + - variable: app_id + label: + zh_Hans: APPID + en_US: APPID + type: text-input + required: true + placeholder: + zh_Hans: 在此输入您的腾讯语音识别服务的 APPID + en_US: Enter the APPID of your Tencent Cloud ASR service + - variable: secret_id + label: + zh_Hans: SecretId + en_US: SecretId + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的腾讯语音识别服务的 SecretId + en_US: Enter the SecretId of your Tencent Cloud ASR service + - variable: secret_key + label: + zh_Hans: SecretKey + en_US: SecretKey + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的腾讯语音识别服务的 SecretKey + en_US: Enter the SecretKey of your Tencent Cloud ASR service From 16907888274b2cc2af243e35d2e8019aa43bee00 Mon Sep 17 00:00:00 2001 From: takatost Date: Tue, 23 Jul 2024 16:48:21 +0800 Subject: [PATCH 4/8] fix: name 'current_app' is not defined in recommended_app_service (#6574) --- api/services/recommended_app_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/services/recommended_app_service.py b/api/services/recommended_app_service.py index 20d21c22a9..1c1c5be17c 100644 --- a/api/services/recommended_app_service.py +++ b/api/services/recommended_app_service.py @@ -4,6 +4,7 @@ from os import path from typing import Optional import requests +from flask import current_app from configs import dify_config from constants.languages import languages From c0ada940bdb4d633f29030a7b955c4fb784206d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 23 Jul 2024 17:00:39 +0800 Subject: [PATCH 5/8] fix: tool params not work as expected when develop a tool (#6550) --- .../tools/utils/tool_parameter_converter.py | 2 +- .../components/variable/constant-field.tsx | 62 +++++++++++++++++++ .../variable/var-reference-picker.tsx | 21 +++---- .../nodes/tool/components/input-var-list.tsx | 26 +++++--- 4 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx diff --git a/api/core/tools/utils/tool_parameter_converter.py b/api/core/tools/utils/tool_parameter_converter.py index 0c4ec00ec6..6f88eeaa0a 100644 --- a/api/core/tools/utils/tool_parameter_converter.py +++ b/api/core/tools/utils/tool_parameter_converter.py @@ -53,7 +53,7 @@ class ToolParameterConverter: case ToolParameter.ToolParameterType.NUMBER: if isinstance(value, int) | isinstance(value, float): return value - elif isinstance(value, str): + elif isinstance(value, str) and value != '': if '.' in value: return float(value) else: diff --git a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx new file mode 100644 index 0000000000..bd7d159906 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx @@ -0,0 +1,62 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import type { Var } from '@/app/components/workflow/types' +import { SimpleSelect } from '@/app/components/base/select' + +type Props = { + schema: CredentialFormSchema + readonly: boolean + value: string + onChange: (value: string | number, varKindType: VarKindType, varInfo?: Var) => void +} + +const ConstantField: FC = ({ + schema, + readonly, + value, + onChange, +}) => { + const language = useLanguage() + const placeholder = (schema as CredentialFormSchemaSelect).placeholder + const handleStaticChange = useCallback((e: React.ChangeEvent) => { + const value = e.target.value === '' ? '' : parseFloat(e.target.value) + onChange(value, VarKindType.constant) + }, [onChange]) + const handleSelectChange = useCallback((value: string | number) => { + value = value === null ? '' : value + onChange(value as string, VarKindType.constant) + }, [onChange]) + + return ( + <> + {schema.type === FormTypeEnum.select && ( + ({ value: option.value, name: option.label[language] || option.label.en_US }))} + onSelect={item => handleSelectChange(item.value)} + placeholder={placeholder?.[language] || placeholder?.en_US} + /> + )} + {schema.type === FormTypeEnum.textNumber && ( + + )} + + ) +} +export default React.memo(ConstantField) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 1e041ba5b1..bd8d5db88f 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -10,8 +10,10 @@ import produce from 'immer' import { useStoreApi } from 'reactflow' import VarReferencePopup from './var-reference-popup' import { getNodeInfoById, isENV, isSystemVar } from './utils' +import ConstantField from './constant-field' import cn from '@/utils/classnames' import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import { BlockEnum } from '@/app/components/workflow/types' import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' @@ -47,6 +49,7 @@ type Props = { availableNodes?: Node[] availableVars?: NodeOutPutVar[] isAddBtnTrigger?: boolean + schema?: CredentialFormSchema } const VarReferencePicker: FC = ({ @@ -64,6 +67,7 @@ const VarReferencePicker: FC = ({ availableNodes: passedInAvailableNodes, availableVars, isAddBtnTrigger, + schema, }) => { const { t } = useTranslation() const store = useStoreApi() @@ -192,10 +196,6 @@ const VarReferencePicker: FC = ({ setOpen(false) }, [onChange, varKindType]) - const handleStaticChange = useCallback((e: React.ChangeEvent) => { - onChange(e.target.value as string, varKindType) - }, [onChange, varKindType]) - const handleClearVar = useCallback(() => { if (varKindType === VarKindType.constant) onChange('', varKindType) @@ -265,14 +265,11 @@ const VarReferencePicker: FC = ({ )} {isConstant ? ( - setIsFocus(true)} - onBlur={() => setIsFocus(false)} - readOnly={readonly} + void)} + schema={schema as CredentialFormSchema} + readonly={readonly} /> ) : ( diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 3f8447a557..bfa4a542bd 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -48,6 +48,8 @@ const InputVarList: FC = ({ return 'Number' else if (type === FormTypeEnum.files) return 'Files' + else if (type === FormTypeEnum.select) + return 'Options' else return 'String' } @@ -114,17 +116,19 @@ const InputVarList: FC = ({ return (
{ - schema.map(({ - variable, - label, - type, - required, - tooltip, - }, index) => { + schema.map((schema, index) => { + const { + variable, + label, + type, + required, + tooltip, + } = schema const varInput = value[variable] const isNumber = type === FormTypeEnum.textNumber + const isSelect = type === FormTypeEnum.select const isFile = type === FormTypeEnum.files - const isString = type !== FormTypeEnum.textNumber && type !== FormTypeEnum.files + const isString = type !== FormTypeEnum.textNumber && type !== FormTypeEnum.files && type !== FormTypeEnum.select return (
@@ -145,7 +149,7 @@ const InputVarList: FC = ({ placeholderClassName='!leading-[21px]' /> )} - {isNumber && ( + {(isNumber || isSelect) && ( = ({ onOpen={handleOpen(index)} isSupportConstantValue={isSupportConstantValue} defaultVarKindType={varInput?.type} - filterVar={filterVar} + filterVar={isNumber ? filterVar : undefined} + availableVars={isSelect ? availableVars : undefined} + schema={schema} /> )} {isFile && ( From ad7552ea8dd14adbbd6df7847878103da32d967f Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 23 Jul 2024 17:09:16 +0800 Subject: [PATCH 6/8] fix(api/core/workflow/nodes/llm/llm_node.py): Fix LLM Node error. (#6576) --- api/core/workflow/nodes/llm/llm_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/llm/llm_node.py b/api/core/workflow/nodes/llm/llm_node.py index 7fb3333a3a..4431259a57 100644 --- a/api/core/workflow/nodes/llm/llm_node.py +++ b/api/core/workflow/nodes/llm/llm_node.py @@ -364,7 +364,7 @@ class LLMNode(BaseNode): if 'content' not in item: raise ValueError(f'Invalid context structure: {item}') - context_str += item['content'].text + '\n' + context_str += item['content'] + '\n' retriever_resource = self._convert_to_original_retriever_resource(item) if retriever_resource: From 6a9d2024147a34d69330681a551254ac2113ef7c Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 23 Jul 2024 17:11:02 +0800 Subject: [PATCH 7/8] chore: layout UI upgrade (#6577) --- web/app/(commonLayout)/layout.tsx | 2 +- web/app/components/app-sidebar/app-info.tsx | 2 +- web/app/components/app-sidebar/index.tsx | 8 +++---- web/app/components/app-sidebar/navLink.tsx | 3 +-- web/app/components/base/logo/logo-site.tsx | 11 +++++++++- .../components/header/explore-nav/index.tsx | 4 ++-- .../{HeaderWrapper.tsx => header-wrapper.tsx} | 2 +- web/app/components/header/nav/index.tsx | 8 +++---- .../header/nav/nav-selector/index.tsx | 4 ++-- web/context/app-context.tsx | 20 +++++++++++++++++- web/public/logo/logo-site-dark.png | Bin 0 -> 4270 bytes web/tailwind.config.js | 1 + web/types/app.ts | 5 +++++ 13 files changed, 51 insertions(+), 19 deletions(-) rename web/app/components/header/{HeaderWrapper.tsx => header-wrapper.tsx} (84%) create mode 100644 web/public/logo/logo-site-dark.png diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index ceeb164315..af36d4d961 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -3,7 +3,7 @@ import type { ReactNode } from 'react' import SwrInitor from '@/app/components/swr-initor' import { AppContextProvider } from '@/context/app-context' import GA, { GaType } from '@/app/components/base/ga' -import HeaderWrapper from '@/app/components/header/HeaderWrapper' +import HeaderWrapper from '@/app/components/header/header-wrapper' import Header from '@/app/components/header' import { EventEmitterContextProvider } from '@/context/event-emitter' import { ProviderContextProvider } from '@/context/provider-context' diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index ef37ff3c78..90d3717d2c 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -218,7 +218,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
{expand && (
-
+
{appDetail.name}
{isCurrentWorkspaceEditor && }
diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 9a78cd7e6e..5d5d407dc0 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -49,7 +49,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf return (
@@ -60,7 +60,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf `} > {iconType === 'app' && ( - + )} {iconType !== 'app' && ( {!expand && ( -
+
)}