mirror of https://github.com/langgenius/dify.git
Merge branch 'main' into feat/tool-oauth
This commit is contained in:
commit
119d41099d
|
|
@ -17,6 +17,11 @@ APP_WEB_URL=http://127.0.0.1:3000
|
|||
# Files URL
|
||||
FILES_URL=http://127.0.0.1:5001
|
||||
|
||||
# INTERNAL_FILES_URL is used for plugin daemon communication within Docker network.
|
||||
# Set this to the internal Docker service URL for proper plugin file access.
|
||||
# Example: INTERNAL_FILES_URL=http://api:5001
|
||||
INTERNAL_FILES_URL=http://127.0.0.1:5001
|
||||
|
||||
# The time in seconds after the signature is rejected
|
||||
FILES_ACCESS_TIMEOUT=300
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,13 @@ class FileAccessConfig(BaseSettings):
|
|||
default="",
|
||||
)
|
||||
|
||||
INTERNAL_FILES_URL: str = Field(
|
||||
description="Internal base URL for file access within Docker network,"
|
||||
" used for plugin daemon and internal service communication."
|
||||
" Falls back to FILES_URL if not specified.",
|
||||
default="",
|
||||
)
|
||||
|
||||
FILES_ACCESS_TIMEOUT: int = Field(
|
||||
description="Expiration time in seconds for file access URLs",
|
||||
default=300,
|
||||
|
|
|
|||
|
|
@ -90,7 +90,11 @@ class AppMCPServerRefreshController(Resource):
|
|||
def get(self, server_id):
|
||||
if not current_user.is_editor:
|
||||
raise NotFound()
|
||||
server = db.session.query(AppMCPServer).filter(AppMCPServer.id == server_id).first()
|
||||
server = (
|
||||
db.session.query(AppMCPServer)
|
||||
.filter(AppMCPServer.id == server_id and AppMCPServer.tenant_id == current_user.current_tenant_id)
|
||||
.first()
|
||||
)
|
||||
if not server:
|
||||
raise NotFound()
|
||||
server.server_code = AppMCPServer.generate_server_code(16)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ def get_signed_file_url(upload_file_id: str) -> str:
|
|||
|
||||
|
||||
def get_signed_file_url_for_plugin(filename: str, mimetype: str, tenant_id: str, user_id: str) -> str:
|
||||
url = f"{dify_config.FILES_URL}/files/upload/for-plugin"
|
||||
# Plugin access should use internal URL for Docker network communication
|
||||
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
|
||||
url = f"{base_url}/files/upload/for-plugin"
|
||||
|
||||
if user_id is None:
|
||||
user_id = "DEFAULT-USER"
|
||||
|
|
|
|||
|
|
@ -112,13 +112,13 @@ class MCPServerStreamableHTTPRequestHandler:
|
|||
def initialize(self):
|
||||
request = cast(types.InitializeRequest, self.request.root)
|
||||
client_info = request.params.clientInfo
|
||||
clinet_name = f"{client_info.name}@{client_info.version}"
|
||||
client_name = f"{client_info.name}@{client_info.version}"
|
||||
if not self.end_user:
|
||||
end_user = EndUser(
|
||||
tenant_id=self.app.tenant_id,
|
||||
app_id=self.app.id,
|
||||
type="mcp",
|
||||
name=clinet_name,
|
||||
name=client_name,
|
||||
session_id=generate_session_id(),
|
||||
external_user_id=self.mcp_server.id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,19 +39,22 @@ class ApiToolProviderController(ToolProviderController):
|
|||
type=ProviderConfig.Type.SELECT,
|
||||
options=[
|
||||
ProviderConfig.Option(value="none", label=I18nObject(en_US="None", zh_Hans="无")),
|
||||
ProviderConfig.Option(value="api_key", label=I18nObject(en_US="api_key", zh_Hans="api_key")),
|
||||
ProviderConfig.Option(value="api_key_header", label=I18nObject(en_US="Header", zh_Hans="请求头")),
|
||||
ProviderConfig.Option(
|
||||
value="api_key_query", label=I18nObject(en_US="Query Param", zh_Hans="查询参数")
|
||||
),
|
||||
],
|
||||
default="none",
|
||||
help=I18nObject(en_US="The auth type of the api provider", zh_Hans="api provider 的认证类型"),
|
||||
)
|
||||
]
|
||||
if auth_type == ApiProviderAuthType.API_KEY:
|
||||
if auth_type == ApiProviderAuthType.API_KEY_HEADER:
|
||||
credentials_schema = [
|
||||
*credentials_schema,
|
||||
ProviderConfig(
|
||||
name="api_key_header",
|
||||
required=False,
|
||||
default="api_key",
|
||||
default="Authorization",
|
||||
type=ProviderConfig.Type.TEXT_INPUT,
|
||||
help=I18nObject(en_US="The header name of the api key", zh_Hans="携带 api key 的 header 名称"),
|
||||
),
|
||||
|
|
@ -74,6 +77,25 @@ class ApiToolProviderController(ToolProviderController):
|
|||
],
|
||||
),
|
||||
]
|
||||
elif auth_type == ApiProviderAuthType.API_KEY_QUERY:
|
||||
credentials_schema = [
|
||||
*credentials_schema,
|
||||
ProviderConfig(
|
||||
name="api_key_query_param",
|
||||
required=False,
|
||||
default="key",
|
||||
type=ProviderConfig.Type.TEXT_INPUT,
|
||||
help=I18nObject(
|
||||
en_US="The query parameter name of the api key", zh_Hans="携带 api key 的查询参数名称"
|
||||
),
|
||||
),
|
||||
ProviderConfig(
|
||||
name="api_key_value",
|
||||
required=True,
|
||||
type=ProviderConfig.Type.SECRET_INPUT,
|
||||
help=I18nObject(en_US="The api key", zh_Hans="api key 的值"),
|
||||
),
|
||||
]
|
||||
elif auth_type == ApiProviderAuthType.NONE:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ class ApiTool(Tool):
|
|||
if "auth_type" not in credentials:
|
||||
raise ToolProviderCredentialValidationError("Missing auth_type")
|
||||
|
||||
if credentials["auth_type"] == "api_key":
|
||||
api_key_header = "api_key"
|
||||
if credentials["auth_type"] in ("api_key_header", "api_key"): # backward compatibility:
|
||||
api_key_header = "Authorization"
|
||||
|
||||
if "api_key_header" in credentials:
|
||||
api_key_header = credentials["api_key_header"]
|
||||
|
|
@ -100,6 +100,11 @@ class ApiTool(Tool):
|
|||
|
||||
headers[api_key_header] = credentials["api_key_value"]
|
||||
|
||||
elif credentials["auth_type"] == "api_key_query":
|
||||
# For query parameter authentication, we don't add anything to headers
|
||||
# The query parameter will be added in do_http_request method
|
||||
pass
|
||||
|
||||
needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required]
|
||||
for parameter in needed_parameters:
|
||||
if parameter.required and parameter.name not in parameters:
|
||||
|
|
@ -154,6 +159,15 @@ class ApiTool(Tool):
|
|||
cookies = {}
|
||||
files = []
|
||||
|
||||
# Add API key to query parameters if auth_type is api_key_query
|
||||
if self.runtime and self.runtime.credentials:
|
||||
credentials = self.runtime.credentials
|
||||
if credentials.get("auth_type") == "api_key_query":
|
||||
api_key_query_param = credentials.get("api_key_query_param", "key")
|
||||
api_key_value = credentials.get("api_key_value")
|
||||
if api_key_value:
|
||||
params[api_key_query_param] = api_key_value
|
||||
|
||||
# check parameters
|
||||
for parameter in self.api_bundle.openapi.get("parameters", []):
|
||||
value = self.get_parameter_value(parameter, parameters)
|
||||
|
|
@ -213,7 +227,8 @@ class ApiTool(Tool):
|
|||
elif "default" in property:
|
||||
body[name] = property["default"]
|
||||
else:
|
||||
body[name] = None
|
||||
# omit optional parameters that weren't provided, instead of setting them to None
|
||||
pass
|
||||
break
|
||||
|
||||
# replace path parameters
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ class ApiProviderAuthType(Enum):
|
|||
"""
|
||||
|
||||
NONE = "none"
|
||||
API_KEY = "api_key"
|
||||
API_KEY_HEADER = "api_key_header"
|
||||
API_KEY_QUERY = "api_key_query"
|
||||
|
||||
@classmethod
|
||||
def value_of(cls, value: str) -> "ApiProviderAuthType":
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ from configs import dify_config
|
|||
|
||||
def sign_tool_file(tool_file_id: str, extension: str) -> str:
|
||||
"""
|
||||
sign file to get a temporary url
|
||||
sign file to get a temporary url for plugin access
|
||||
"""
|
||||
base_url = dify_config.FILES_URL
|
||||
# Use internal URL for plugin/tool file access in Docker environments
|
||||
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
|
||||
file_preview_url = f"{base_url}/files/tools/{tool_file_id}{extension}"
|
||||
|
||||
timestamp = str(int(time.time()))
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ class ToolFileManager:
|
|||
@staticmethod
|
||||
def sign_file(tool_file_id: str, extension: str) -> str:
|
||||
"""
|
||||
sign file to get a temporary url
|
||||
sign file to get a temporary url for plugin access
|
||||
"""
|
||||
base_url = dify_config.FILES_URL
|
||||
# Use internal URL for plugin/tool file access in Docker environments
|
||||
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
|
||||
file_preview_url = f"{base_url}/files/tools/{tool_file_id}{extension}"
|
||||
|
||||
timestamp = str(int(time.time()))
|
||||
|
|
|
|||
|
|
@ -684,9 +684,16 @@ class ToolManager:
|
|||
if provider is None:
|
||||
raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
|
||||
|
||||
auth_type = ApiProviderAuthType.NONE
|
||||
provider_auth_type = provider.credentials.get("auth_type")
|
||||
if provider_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||
elif provider_auth_type == "api_key_query":
|
||||
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||
|
||||
controller = ApiToolProviderController.from_db(
|
||||
provider,
|
||||
ApiProviderAuthType.API_KEY if provider.credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE,
|
||||
auth_type,
|
||||
)
|
||||
controller.load_bundled_tools(provider.tools)
|
||||
|
||||
|
|
@ -745,9 +752,16 @@ class ToolManager:
|
|||
credentials = {}
|
||||
|
||||
# package tool provider controller
|
||||
auth_type = ApiProviderAuthType.NONE
|
||||
credentials_auth_type = credentials.get("auth_type")
|
||||
if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||
elif credentials_auth_type == "api_key_query":
|
||||
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||
|
||||
controller = ApiToolProviderController.from_db(
|
||||
provider_obj,
|
||||
ApiProviderAuthType.API_KEY if credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE,
|
||||
auth_type,
|
||||
)
|
||||
# init tool configuration
|
||||
tool_configuration = ProviderConfigEncrypter(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "dify-api"
|
||||
version = "1.5.1"
|
||||
version = "1.6.0"
|
||||
requires-python = ">=3.11,<3.13"
|
||||
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ class MCPToolManageService:
|
|||
MCPToolProvider.server_url_hash == server_url_hash,
|
||||
MCPToolProvider.server_identifier == server_identifier,
|
||||
),
|
||||
MCPToolProvider.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -159,11 +159,16 @@ class ToolTransformService:
|
|||
convert provider controller to user provider
|
||||
"""
|
||||
# package tool provider controller
|
||||
auth_type = ApiProviderAuthType.NONE
|
||||
credentials_auth_type = db_provider.credentials.get("auth_type")
|
||||
if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||
elif credentials_auth_type == "api_key_query":
|
||||
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||
|
||||
controller = ApiToolProviderController.from_db(
|
||||
db_provider=db_provider,
|
||||
auth_type=ApiProviderAuthType.API_KEY
|
||||
if db_provider.credentials["auth_type"] == "api_key"
|
||||
else ApiProviderAuthType.NONE,
|
||||
auth_type=auth_type,
|
||||
)
|
||||
|
||||
return controller
|
||||
|
|
|
|||
|
|
@ -1217,7 +1217,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "dify-api"
|
||||
version = "1.5.1"
|
||||
version = "1.6.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "arize-phoenix-otel" },
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ APP_WEB_URL=
|
|||
# ensuring port 5001 is externally accessible (see docker-compose.yaml).
|
||||
FILES_URL=
|
||||
|
||||
# INTERNAL_FILES_URL is used for plugin daemon communication within Docker network.
|
||||
# Set this to the internal Docker service URL for proper plugin file access.
|
||||
# Example: INTERNAL_FILES_URL=http://api:5001
|
||||
INTERNAL_FILES_URL=
|
||||
|
||||
# ------------------------------
|
||||
# Server Configuration
|
||||
# ------------------------------
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.5.1
|
||||
image: langgenius/dify-api:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -31,7 +31,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.5.1
|
||||
image: langgenius/dify-api:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -57,7 +57,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.5.1
|
||||
image: langgenius/dify-web:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ x-shared-env: &shared-api-worker-env
|
|||
APP_API_URL: ${APP_API_URL:-}
|
||||
APP_WEB_URL: ${APP_WEB_URL:-}
|
||||
FILES_URL: ${FILES_URL:-}
|
||||
INTERNAL_FILES_URL: ${INTERNAL_FILES_URL:-}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||
LOG_FILE: ${LOG_FILE:-/app/logs/server.log}
|
||||
LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20}
|
||||
|
|
@ -518,7 +519,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.5.1
|
||||
image: langgenius/dify-api:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -547,7 +548,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.5.1
|
||||
image: langgenius/dify-api:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -573,7 +574,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.5.1
|
||||
image: langgenius/dify-web:1.6.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import { useContext } from 'use-context-selector'
|
||||
|
||||
import { Microphone01 } from '@/app/components/base/icons/src/vender/features'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
|
||||
const ConfigAudio: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const file = useFeatures(s => s.features.file)
|
||||
const featuresStore = useFeaturesStore()
|
||||
const { isShowAudioConfig } = useContext(ConfigContext)
|
||||
|
||||
const isAudioEnabled = file?.allowed_file_types?.includes(SupportUploadFileTypes.audio) ?? false
|
||||
|
||||
const handleChange = useCallback((value: boolean) => {
|
||||
const {
|
||||
features,
|
||||
setFeatures,
|
||||
} = featuresStore!.getState()
|
||||
|
||||
const newFeatures = produce(features, (draft) => {
|
||||
if (value) {
|
||||
draft.file!.allowed_file_types = Array.from(new Set([
|
||||
...(draft.file?.allowed_file_types || []),
|
||||
SupportUploadFileTypes.audio,
|
||||
]))
|
||||
}
|
||||
else {
|
||||
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
|
||||
type => type !== SupportUploadFileTypes.audio,
|
||||
)
|
||||
}
|
||||
if (draft.file)
|
||||
draft.file.enabled = (draft.file.allowed_file_types?.length ?? 0) > 0
|
||||
})
|
||||
setFeatures(newFeatures)
|
||||
}, [featuresStore])
|
||||
|
||||
if (!isShowAudioConfig)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='mt-2 flex items-center gap-2 rounded-xl border-l-[0.5px] border-t-[0.5px] bg-background-section-burn p-2'>
|
||||
<div className='shrink-0 p-1'>
|
||||
<div className='rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-violet-violet-600 p-1 shadow-xs'>
|
||||
<Microphone01 className='h-4 w-4 text-text-primary-on-surface' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex grow items-center'>
|
||||
<div className='system-sm-semibold mr-1 text-text-secondary'>{t('appDebug.feature.audioUpload.title')}</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]' >
|
||||
{t('appDebug.feature.audioUpload.description')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center'>
|
||||
<div className='ml-1 mr-3 h-3.5 w-[1px] bg-divider-subtle'></div>
|
||||
<Switch
|
||||
defaultValue={isAudioEnabled}
|
||||
onChange={handleChange}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ConfigAudio)
|
||||
|
|
@ -8,6 +8,7 @@ import DatasetConfig from '../dataset-config'
|
|||
import HistoryPanel from '../config-prompt/conversation-history/history-panel'
|
||||
import ConfigVision from '../config-vision'
|
||||
import ConfigDocument from './config-document'
|
||||
import ConfigAudio from './config-audio'
|
||||
import AgentTools from './agent/agent-tools'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||
|
|
@ -85,6 +86,8 @@ const Config: FC = () => {
|
|||
|
||||
<ConfigDocument />
|
||||
|
||||
<ConfigAudio />
|
||||
|
||||
{/* Chat History */}
|
||||
{isAdvancedMode && isChatApp && modelModeType === ModelModeType.completion && (
|
||||
<HistoryPanel
|
||||
|
|
|
|||
|
|
@ -474,6 +474,7 @@ const Configuration: FC = () => {
|
|||
|
||||
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
|
||||
const isShowDocumentConfig = !!currModel?.features?.includes(ModelFeatureEnum.document)
|
||||
const isShowAudioConfig = !!currModel?.features?.includes(ModelFeatureEnum.audio)
|
||||
const isAllowVideoUpload = !!currModel?.features?.includes(ModelFeatureEnum.video)
|
||||
// *** web app features ***
|
||||
const featuresData: FeaturesData = useMemo(() => {
|
||||
|
|
@ -920,6 +921,7 @@ const Configuration: FC = () => {
|
|||
setVisionConfig: handleSetVisionConfig,
|
||||
isAllowVideoUpload,
|
||||
isShowDocumentConfig,
|
||||
isShowAudioConfig,
|
||||
rerankSettingModalOpen,
|
||||
setRerankSettingModalOpen,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
|||
return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
|
||||
|
||||
return (
|
||||
<div className='flex flex-col bg-components-panel-bg' style={{ height: 'calc(100vh - 56px)' }}>
|
||||
<div className='flex flex-col overflow-hidden bg-components-panel-bg' style={{ height: 'calc(100vh - 56px)' }}>
|
||||
<TopBar activeIndex={step - 1} datasetId={datasetId} />
|
||||
<div style={{ height: 'calc(100% - 52px)' }}>
|
||||
{step === 1 && <StepOne
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
.filePreview {
|
||||
@apply flex flex-col border-l border-components-panel-border shrink-0 bg-background-default-lighter;
|
||||
width: 528px;
|
||||
}
|
||||
|
||||
.previewHeader {
|
||||
|
|
@ -28,6 +27,7 @@
|
|||
background: #f9fafb center no-repeat url(../assets/Loading.svg);
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.fileContent {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightLine, RiFolder6Line } from '@remixicon/react'
|
||||
import FilePreview from '../file-preview'
|
||||
|
|
@ -95,24 +95,29 @@ const StepOne = ({
|
|||
const modalShowHandle = () => setShowModal(true)
|
||||
const modalCloseHandle = () => setShowModal(false)
|
||||
|
||||
const updateCurrentFile = (file: File) => {
|
||||
const updateCurrentFile = useCallback((file: File) => {
|
||||
setCurrentFile(file)
|
||||
}
|
||||
const hideFilePreview = () => {
|
||||
}, [])
|
||||
|
||||
const hideFilePreview = useCallback(() => {
|
||||
setCurrentFile(undefined)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const updateCurrentPage = (page: NotionPage) => {
|
||||
const updateCurrentPage = useCallback((page: NotionPage) => {
|
||||
setCurrentNotionPage(page)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const hideNotionPagePreview = () => {
|
||||
const hideNotionPagePreview = useCallback(() => {
|
||||
setCurrentNotionPage(undefined)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const hideWebsitePreview = () => {
|
||||
const updateWebsite = useCallback((website: CrawlResultItem) => {
|
||||
setCurrentWebsite(website)
|
||||
}, [])
|
||||
|
||||
const hideWebsitePreview = useCallback(() => {
|
||||
setCurrentWebsite(undefined)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
|
||||
const isInCreatePage = shouldShowDataSourceTypeList
|
||||
|
|
@ -139,7 +144,7 @@ const StepOne = ({
|
|||
<div className={classNames(s.form)}>
|
||||
{
|
||||
shouldShowDataSourceTypeList && (
|
||||
<div className={classNames(s.stepHeader, 'text-text-secondary system-md-semibold')}>
|
||||
<div className={classNames(s.stepHeader, 'system-md-semibold text-text-secondary')}>
|
||||
{t('datasetCreation.steps.one')}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -158,8 +163,8 @@ const StepOne = ({
|
|||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.FILE)
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
hideWebsitePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon)} />
|
||||
|
|
@ -182,7 +187,7 @@ const StepOne = ({
|
|||
return
|
||||
changeType(DataSourceType.NOTION)
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
hideWebsitePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.notion)} />
|
||||
|
|
@ -201,7 +206,13 @@ const StepOne = ({
|
|||
dataSourceType === DataSourceType.WEB && s.active,
|
||||
dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled,
|
||||
)}
|
||||
onClick={() => changeType(DataSourceType.WEB)}
|
||||
onClick={() => {
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.WEB)
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.web)} />
|
||||
<span
|
||||
|
|
@ -276,7 +287,7 @@ const StepOne = ({
|
|||
<>
|
||||
<div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}>
|
||||
<Website
|
||||
onPreview={setCurrentWebsite}
|
||||
onPreview={updateWebsite}
|
||||
checkedCrawlResult={websitePages}
|
||||
onCheckedCrawlResultChange={updateWebsitePages}
|
||||
onCrawlProviderChange={onWebsiteCrawlProviderChange}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const Header = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='flex h-[60px] items-center'>
|
||||
<div className='flex h-[56px] items-center'>
|
||||
<div className='flex min-w-0 flex-[1] items-center pl-3 pr-2 min-[1280px]:pr-3'>
|
||||
<Link href="/apps" className='flex h-8 shrink-0 items-center justify-center px-0.5'>
|
||||
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
|
|
|
|||
|
|
@ -68,23 +68,34 @@ const ConfigCredential: FC<Props> = ({
|
|||
text={t('tools.createTool.authMethod.types.none')}
|
||||
value={AuthType.none}
|
||||
isChecked={tempCredential.auth_type === AuthType.none}
|
||||
onClick={value => setTempCredential({ ...tempCredential, auth_type: value as AuthType })}
|
||||
onClick={value => setTempCredential({
|
||||
auth_type: value as AuthType,
|
||||
})}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authMethod.types.api_key')}
|
||||
value={AuthType.apiKey}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKey}
|
||||
text={t('tools.createTool.authMethod.types.api_key_header')}
|
||||
value={AuthType.apiKeyHeader}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyHeader}
|
||||
onClick={value => setTempCredential({
|
||||
...tempCredential,
|
||||
auth_type: value as AuthType,
|
||||
api_key_header: tempCredential.api_key_header || 'Authorization',
|
||||
api_key_value: tempCredential.api_key_value || '',
|
||||
api_key_header_prefix: tempCredential.api_key_header_prefix || AuthHeaderPrefix.custom,
|
||||
})}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authMethod.types.api_key_query')}
|
||||
value={AuthType.apiKeyQuery}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
|
||||
onClick={value => setTempCredential({
|
||||
auth_type: value as AuthType,
|
||||
api_key_query_param: tempCredential.api_key_query_param || 'key',
|
||||
api_key_value: tempCredential.api_key_value || '',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{tempCredential.auth_type === AuthType.apiKey && (
|
||||
{tempCredential.auth_type === AuthType.apiKeyHeader && (
|
||||
<>
|
||||
<div>
|
||||
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.authHeaderPrefix.title')}</div>
|
||||
|
|
@ -136,6 +147,35 @@ const ConfigCredential: FC<Props> = ({
|
|||
/>
|
||||
</div>
|
||||
</>)}
|
||||
{tempCredential.auth_type === AuthType.apiKeyQuery && (
|
||||
<>
|
||||
<div>
|
||||
<div className='system-sm-medium flex items-center py-2 text-text-primary'>
|
||||
{t('tools.createTool.authMethod.queryParam')}
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[261px] text-text-tertiary'>
|
||||
{t('tools.createTool.authMethod.queryParamTooltip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_query_param}
|
||||
onChange={e => setTempCredential({ ...tempCredential, api_key_query_param: e.target.value })}
|
||||
placeholder={t('tools.createTool.authMethod.types.queryParamPlaceholder')!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.authMethod.value')}</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={e => setTempCredential({ ...tempCredential, api_key_value: e.target.value })}
|
||||
placeholder={t('tools.createTool.authMethod.types.apiValuePlaceholder')!}
|
||||
/>
|
||||
</div>
|
||||
</>)}
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export enum LOC {
|
|||
|
||||
export enum AuthType {
|
||||
none = 'none',
|
||||
apiKey = 'api_key',
|
||||
apiKeyHeader = 'api_key_header',
|
||||
apiKeyQuery = 'api_key_query',
|
||||
}
|
||||
|
||||
export enum AuthHeaderPrefix {
|
||||
|
|
@ -21,6 +22,7 @@ export type Credential = {
|
|||
api_key_header?: string
|
||||
api_key_value?: string
|
||||
api_key_header_prefix?: AuthHeaderPrefix
|
||||
api_key_query_param?: string
|
||||
}
|
||||
|
||||
export enum CollectionType {
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
|||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if(!readOnly) return
|
||||
if(readOnly) return
|
||||
if (advancedEditing || isAddingNewField) return
|
||||
setHoveringPropertyDebounced(path.join('.'))
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if(!readOnly) return
|
||||
if(readOnly) return
|
||||
if (advancedEditing || isAddingNewField) return
|
||||
setHoveringPropertyDebounced(null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ type IDebugConfiguration = {
|
|||
setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void
|
||||
isAllowVideoUpload: boolean
|
||||
isShowDocumentConfig: boolean
|
||||
isShowAudioConfig: boolean
|
||||
rerankSettingModalOpen: boolean
|
||||
setRerankSettingModalOpen: (rerankSettingModalOpen: boolean) => void
|
||||
}
|
||||
|
|
@ -254,6 +255,7 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
|
|||
setVisionConfig: noop,
|
||||
isAllowVideoUpload: false,
|
||||
isShowDocumentConfig: false,
|
||||
isShowAudioConfig: false,
|
||||
rerankSettingModalOpen: false,
|
||||
setRerankSettingModalOpen: noop,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ const translation = {
|
|||
title: 'Document',
|
||||
description: 'Enable Document will allows the model to take in documents and answer questions about them.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Enable Audio will allow the model to process audio files for transcription and analysis.',
|
||||
},
|
||||
},
|
||||
codegen: {
|
||||
title: 'Code Generator',
|
||||
|
|
|
|||
|
|
@ -80,11 +80,15 @@ const translation = {
|
|||
title: 'Authorization method',
|
||||
type: 'Authorization type',
|
||||
keyTooltip: 'Http Header Key, You can leave it with "Authorization" if you have no idea what it is or set it to a custom value',
|
||||
queryParam: 'Query Parameter',
|
||||
queryParamTooltip: 'The name of the API key query parameter to pass, e.g. "key" in "https://example.com/test?key=API_KEY".',
|
||||
types: {
|
||||
none: 'None',
|
||||
api_key: 'API Key',
|
||||
api_key_header: 'Header',
|
||||
api_key_query: 'Query Param',
|
||||
apiKeyPlaceholder: 'HTTP header name for API Key',
|
||||
apiValuePlaceholder: 'Enter API Key',
|
||||
queryParamPlaceholder: 'Query parameter name for API Key',
|
||||
},
|
||||
key: 'Key',
|
||||
value: 'Value',
|
||||
|
|
|
|||
|
|
@ -80,11 +80,15 @@ const translation = {
|
|||
title: '鉴权方法',
|
||||
type: '鉴权类型',
|
||||
keyTooltip: 'HTTP 头部名称,如果你不知道是什么,可以将其保留为 Authorization 或设置为自定义值',
|
||||
queryParam: '查询参数',
|
||||
queryParamTooltip: '用于传递 API 密钥查询参数的名称, 如 "https://example.com/test?key=API_KEY" 中的 "key"参数',
|
||||
types: {
|
||||
none: '无',
|
||||
api_key: 'API Key',
|
||||
api_key_header: '请求头',
|
||||
api_key_query: '查询参数',
|
||||
apiKeyPlaceholder: 'HTTP 头部名称,用于传递 API Key',
|
||||
apiValuePlaceholder: '输入 API Key',
|
||||
queryParamPlaceholder: '查询参数名称,用于传递 API Key',
|
||||
},
|
||||
key: '键',
|
||||
value: '值',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dify-web",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=v22.11.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue