Merge branch 'main' into feat/rag-pipeline

pull/21398/head
zxhlyh 1 year ago
commit e2585bc778

@ -754,7 +754,7 @@ class ProviderConfiguration(BaseModel):
:param only_active: return active model only :param only_active: return active model only
:return: :return:
""" """
provider_models = self.get_provider_models(model_type, only_active) provider_models = self.get_provider_models(model_type, only_active, model)
for provider_model in provider_models: for provider_model in provider_models:
if provider_model.model == model: if provider_model.model == model:
@ -763,12 +763,13 @@ class ProviderConfiguration(BaseModel):
return None return None
def get_provider_models( def get_provider_models(
self, model_type: Optional[ModelType] = None, only_active: bool = False self, model_type: Optional[ModelType] = None, only_active: bool = False, model: Optional[str] = None
) -> list[ModelWithProviderEntity]: ) -> list[ModelWithProviderEntity]:
""" """
Get provider models. Get provider models.
:param model_type: model type :param model_type: model type
:param only_active: only active models :param only_active: only active models
:param model: model name
:return: :return:
""" """
model_provider_factory = ModelProviderFactory(self.tenant_id) model_provider_factory = ModelProviderFactory(self.tenant_id)
@ -791,7 +792,10 @@ class ProviderConfiguration(BaseModel):
) )
else: else:
provider_models = self._get_custom_provider_models( provider_models = self._get_custom_provider_models(
model_types=model_types, provider_schema=provider_schema, model_setting_map=model_setting_map model_types=model_types,
provider_schema=provider_schema,
model_setting_map=model_setting_map,
model=model,
) )
if only_active: if only_active:
@ -943,6 +947,7 @@ class ProviderConfiguration(BaseModel):
model_types: Sequence[ModelType], model_types: Sequence[ModelType],
provider_schema: ProviderEntity, provider_schema: ProviderEntity,
model_setting_map: dict[ModelType, dict[str, ModelSettings]], model_setting_map: dict[ModelType, dict[str, ModelSettings]],
model: Optional[str] = None,
) -> list[ModelWithProviderEntity]: ) -> list[ModelWithProviderEntity]:
""" """
Get custom provider models. Get custom provider models.
@ -995,7 +1000,8 @@ class ProviderConfiguration(BaseModel):
for model_configuration in self.custom_configuration.models: for model_configuration in self.custom_configuration.models:
if model_configuration.model_type not in model_types: if model_configuration.model_type not in model_types:
continue continue
if model and model != model_configuration.model:
continue
try: try:
custom_model_schema = self.get_model_schema( custom_model_schema = self.get_model_schema(
model_type=model_configuration.model_type, model_type=model_configuration.model_type,

@ -234,7 +234,11 @@ class OpsTraceManager:
return None return None
tracing_provider = app_ops_trace_config.get("tracing_provider") tracing_provider = app_ops_trace_config.get("tracing_provider")
if tracing_provider is None or tracing_provider not in provider_config_map: if tracing_provider is None:
return None
try:
provider_config_map[tracing_provider]
except KeyError:
return None return None
# decrypt_token # decrypt_token

@ -190,7 +190,7 @@ class DatasetRetrieval:
retrieve_config.rerank_mode or "reranking_model", retrieve_config.rerank_mode or "reranking_model",
retrieve_config.reranking_model, retrieve_config.reranking_model,
retrieve_config.weights, retrieve_config.weights,
retrieve_config.reranking_enabled or True, True if retrieve_config.reranking_enabled is None else retrieve_config.reranking_enabled,
message_id, message_id,
metadata_filter_document_ids, metadata_filter_document_ids,
metadata_condition, metadata_condition,

@ -11,6 +11,7 @@ from sqlalchemy import UnaryExpression, asc, delete, desc, select
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from core.model_runtime.utils.encoders import jsonable_encoder
from core.workflow.entities.node_entities import NodeRunMetadataKey from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import ( from core.workflow.entities.node_execution_entities import (
NodeExecution, NodeExecution,
@ -171,7 +172,9 @@ class SQLAlchemyWorkflowNodeExecutionRepository(WorkflowNodeExecutionRepository)
db_model.status = domain_model.status db_model.status = domain_model.status
db_model.error = domain_model.error db_model.error = domain_model.error
db_model.elapsed_time = domain_model.elapsed_time db_model.elapsed_time = domain_model.elapsed_time
db_model.execution_metadata = json.dumps(domain_model.metadata) if domain_model.metadata else None db_model.execution_metadata = (
json.dumps(jsonable_encoder(domain_model.metadata)) if domain_model.metadata else None
)
db_model.created_at = domain_model.created_at db_model.created_at = domain_model.created_at
db_model.created_by_role = self._creator_user_role db_model.created_by_role = self._creator_user_role
db_model.created_by = self._creator_user_id db_model.created_by = self._creator_user_id

@ -4,12 +4,14 @@ Unit tests for the SQLAlchemy implementation of WorkflowNodeExecutionRepository.
import json import json
from datetime import datetime from datetime import datetime
from decimal import Decimal
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
import pytest import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.orm import Session, sessionmaker
from core.model_runtime.utils.encoders import jsonable_encoder
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.entities.node_entities import NodeRunMetadataKey from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus
@ -298,7 +300,7 @@ def test_to_db_model(repository):
status=NodeExecutionStatus.RUNNING, status=NodeExecutionStatus.RUNNING,
error=None, error=None,
elapsed_time=1.5, elapsed_time=1.5,
metadata={NodeRunMetadataKey.TOTAL_TOKENS: 100}, metadata={NodeRunMetadataKey.TOTAL_TOKENS: 100, NodeRunMetadataKey.TOTAL_PRICE: Decimal("0.0")},
created_at=datetime.now(), created_at=datetime.now(),
finished_at=None, finished_at=None,
) )
@ -324,7 +326,7 @@ def test_to_db_model(repository):
assert db_model.inputs_dict == domain_model.inputs assert db_model.inputs_dict == domain_model.inputs
assert db_model.process_data_dict == domain_model.process_data assert db_model.process_data_dict == domain_model.process_data
assert db_model.outputs_dict == domain_model.outputs assert db_model.outputs_dict == domain_model.outputs
assert db_model.execution_metadata_dict == domain_model.metadata assert db_model.execution_metadata_dict == jsonable_encoder(domain_model.metadata)
assert db_model.status == domain_model.status assert db_model.status == domain_model.status
assert db_model.error == domain_model.error assert db_model.error == domain_model.error

@ -31,126 +31,98 @@ type Props = {
appDetail: App appDetail: App
} }
const Annotation: FC<Props> = ({ const Annotation: FC<Props> = (props) => {
appDetail, const { appDetail } = props
}) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isShowEdit, setIsShowEdit] = React.useState(false) const [isShowEdit, setIsShowEdit] = useState(false)
const [annotationConfig, setAnnotationConfig] = useState<AnnotationReplyConfig | null>(null) const [annotationConfig, setAnnotationConfig] = useState<AnnotationReplyConfig | null>(null)
const [isChatApp, setIsChatApp] = useState(false) const [isChatApp] = useState(appDetail.mode !== 'completion')
const [controlRefreshSwitch, setControlRefreshSwitch] = useState(Date.now())
const { plan, enableBilling } = useProviderContext()
const isAnnotationFull = enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse
const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false)
const [queryParams, setQueryParams] = useState<QueryParam>({})
const [currPage, setCurrPage] = useState(0)
const [limit, setLimit] = useState(APP_PAGE_LIMIT)
const [list, setList] = useState<AnnotationItem[]>([])
const [total, setTotal] = useState(0)
const [isLoading, setIsLoading] = useState(false)
const [controlUpdateList, setControlUpdateList] = useState(Date.now())
const [currItem, setCurrItem] = useState<AnnotationItem | null>(null)
const [isShowViewModal, setIsShowViewModal] = useState(false)
const debouncedQueryParams = useDebounce(queryParams, { wait: 500 })
const fetchAnnotationConfig = async () => { const fetchAnnotationConfig = async () => {
const res = await doFetchAnnotationConfig(appDetail.id) const res = await doFetchAnnotationConfig(appDetail.id)
setAnnotationConfig(res as AnnotationReplyConfig) setAnnotationConfig(res as AnnotationReplyConfig)
return (res as AnnotationReplyConfig).id return (res as AnnotationReplyConfig).id
} }
useEffect(() => { useEffect(() => {
const isChatApp = appDetail.mode !== 'completion' if (isChatApp) fetchAnnotationConfig()
setIsChatApp(isChatApp) // eslint-disable-next-line react-hooks/exhaustive-deps
if (isChatApp)
fetchAnnotationConfig()
}, []) }, [])
const [controlRefreshSwitch, setControlRefreshSwitch] = useState(Date.now())
const { plan, enableBilling } = useProviderContext()
const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse)
const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false)
const ensureJobCompleted = async (jobId: string, status: AnnotationEnableStatus) => { const ensureJobCompleted = async (jobId: string, status: AnnotationEnableStatus) => {
let isCompleted = false while (true) {
while (!isCompleted) {
const res: any = await queryAnnotationJobStatus(appDetail.id, status, jobId) const res: any = await queryAnnotationJobStatus(appDetail.id, status, jobId)
isCompleted = res.job_status === JobStatus.completed if (res.job_status === JobStatus.completed) break
if (isCompleted)
break
await sleep(2000) await sleep(2000)
} }
} }
const [queryParams, setQueryParams] = useState<QueryParam>({})
const [currPage, setCurrPage] = React.useState<number>(0)
const debouncedQueryParams = useDebounce(queryParams, { wait: 500 })
const [limit, setLimit] = React.useState<number>(APP_PAGE_LIMIT)
const query = {
page: currPage + 1,
limit,
keyword: debouncedQueryParams.keyword || '',
}
const [controlUpdateList, setControlUpdateList] = useState(Date.now())
const [list, setList] = useState<AnnotationItem[]>([])
const [total, setTotal] = useState(10)
const [isLoading, setIsLoading] = useState(false)
const fetchList = async (page = 1) => { const fetchList = async (page = 1) => {
setIsLoading(true) setIsLoading(true)
try { try {
const { data, total }: any = await fetchAnnotationList(appDetail.id, { const { data, total }: any = await fetchAnnotationList(appDetail.id, {
...query,
page, page,
limit,
keyword: debouncedQueryParams.keyword || '',
}) })
setList(data as AnnotationItem[]) setList(data as AnnotationItem[])
setTotal(total) setTotal(total)
} }
catch { finally {
setIsLoading(false)
} }
setIsLoading(false)
} }
useEffect(() => { useEffect(() => {
fetchList(currPage + 1) fetchList(currPage + 1)
}, [currPage]) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currPage, limit, debouncedQueryParams])
useEffect(() => {
fetchList(1)
setControlUpdateList(Date.now())
}, [queryParams])
const handleAdd = async (payload: AnnotationItemBasic) => { const handleAdd = async (payload: AnnotationItemBasic) => {
await addAnnotation(appDetail.id, { await addAnnotation(appDetail.id, payload)
...payload, Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' })
})
Toast.notify({
message: t('common.api.actionSuccess'),
type: 'success',
})
fetchList() fetchList()
setControlUpdateList(Date.now()) setControlUpdateList(Date.now())
} }
const handleRemove = async (id: string) => { const handleRemove = async (id: string) => {
await delAnnotation(appDetail.id, id) await delAnnotation(appDetail.id, id)
Toast.notify({ Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' })
message: t('common.api.actionSuccess'),
type: 'success',
})
fetchList() fetchList()
setControlUpdateList(Date.now()) setControlUpdateList(Date.now())
} }
const [currItem, setCurrItem] = useState<AnnotationItem | null>(list[0])
const [isShowViewModal, setIsShowViewModal] = useState(false)
useEffect(() => {
if (!isShowEdit)
setControlRefreshSwitch(Date.now())
}, [isShowEdit])
const handleView = (item: AnnotationItem) => { const handleView = (item: AnnotationItem) => {
setCurrItem(item) setCurrItem(item)
setIsShowViewModal(true) setIsShowViewModal(true)
} }
const handleSave = async (question: string, answer: string) => { const handleSave = async (question: string, answer: string) => {
await editAnnotation(appDetail.id, (currItem as AnnotationItem).id, { if (!currItem) return
question, await editAnnotation(appDetail.id, currItem.id, { question, answer })
answer, Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' })
})
Toast.notify({
message: t('common.api.actionSuccess'),
type: 'success',
})
fetchList() fetchList()
setControlUpdateList(Date.now()) setControlUpdateList(Date.now())
} }
useEffect(() => {
if (!isShowEdit) setControlRefreshSwitch(Date.now())
}, [isShowEdit])
return ( return (
<div className='flex h-full flex-col'> <div className='flex h-full flex-col'>
<p className='system-sm-regular text-text-tertiary'>{t('appLog.description')}</p> <p className='system-sm-regular text-text-tertiary'>{t('appLog.description')}</p>
@ -211,6 +183,7 @@ const Annotation: FC<Props> = ({
</Filter> </Filter>
{isLoading {isLoading
? <Loading type='app' /> ? <Loading type='app' />
// eslint-disable-next-line sonarjs/no-nested-conditional
: total > 0 : total > 0
? <List ? <List
list={list} list={list}

@ -18,7 +18,7 @@ import style from './style.module.css'
import type { ConfigParams } from './settings' import type { ConfigParams } from './settings'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import AppBasic from '@/app/components/app-sidebar/basic' import AppBasic from '@/app/components/app-sidebar/basic'
import { asyncRunSafe, randomString } from '@/utils' import { asyncRunSafe } from '@/utils'
import { basePath } from '@/utils/var' import { basePath } from '@/utils/var'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
@ -184,7 +184,7 @@ function AppCard({
: t('appOverview.overview.apiInfo.explanation') : t('appOverview.overview.apiInfo.explanation')
} }
/> />
<div className='flex items-center gap-1'> <div className='flex shrink-0 items-center gap-1'>
<Indicator color={runningStatus ? 'green' : 'yellow'} /> <Indicator color={runningStatus ? 'green' : 'yellow'} />
<div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}> <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}>
{runningStatus {runningStatus
@ -210,7 +210,7 @@ function AppCard({
content={isApp ? appUrl : apiUrl} content={isApp ? appUrl : apiUrl}
className={'!size-6'} className={'!size-6'}
/> />
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 rounded-md hover:bg-state-base-hover' selectorId={randomString(8)} />} {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} />}
{isApp && <Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />} {isApp && <Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />}
{/* button copy link/ button regenerate */} {/* button copy link/ button regenerate */}
{showConfirmDelete && ( {showConfirmDelete && (

@ -94,7 +94,7 @@ const ImageInput: FC<UploaderProps> = ({
<div <div
className={classNames( className={classNames(
isDragActive && 'border-primary-600', isDragActive && 'border-primary-600',
'relative aspect-square bg-gray-50 border-[1.5px] border-gray-200 border-dashed rounded-lg flex flex-col justify-center items-center text-gray-500')} 'relative aspect-square border-[1.5px] border-dashed rounded-lg flex flex-col justify-center items-center text-gray-500')}
onDragEnter={handleDragEnter} onDragEnter={handleDragEnter}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}

@ -115,7 +115,7 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
className={cn(s.container, '!w-[362px] !p-0')} className={cn(s.container, '!w-[362px] !p-0')}
> >
{!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="w-full p-2 pb-0"> {!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="w-full p-2 pb-0">
<div className='flex items-center justify-center gap-2 rounded-xl bg-background-body p-1'> <div className='flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary'>
{tabs.map(tab => ( {tabs.map(tab => (
<button <button
key={tab.key} key={tab.key}

@ -4,9 +4,6 @@
align-items: flex-start; align-items: flex-start;
width: 362px; width: 362px;
max-height: 552px; max-height: 552px;
border: 0.5px solid #EAECF0;
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
border-radius: 12px; border-radius: 12px;
background: #fff;
} }

@ -216,16 +216,24 @@ CodeBlock.displayName = 'CodeBlock'
const VideoBlock: any = memo(({ node }: any) => { const VideoBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <VideoGallery key={src} srcs={[src]} />
return null return null
}
return <VideoGallery key={srcs.join()} srcs={srcs} /> return <VideoGallery key={srcs.join()} srcs={srcs} />
}) })
VideoBlock.displayName = 'VideoBlock' VideoBlock.displayName = 'VideoBlock'
const AudioBlock: any = memo(({ node }: any) => { const AudioBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0) if (srcs.length === 0) {
const src = node.properties?.src
if (src)
return <AudioGallery key={src} srcs={[src]} />
return null return null
}
return <AudioGallery key={srcs.join()} srcs={srcs} /> return <AudioGallery key={srcs.join()} srcs={srcs} />
}) })
AudioBlock.displayName = 'AudioBlock' AudioBlock.displayName = 'AudioBlock'

@ -49,8 +49,8 @@ const PluginSettingModal: FC<Props> = ({
</div> </div>
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'> <div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
{[ {[
{ title: t(`${i18nPrefix}.whoCanInstall`), key: 'install_permission', value: tempPrivilege.install_permission }, { title: t(`${i18nPrefix}.whoCanInstall`), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
{ title: t(`${i18nPrefix}.whoCanDebug`), key: 'debug_permission', value: tempPrivilege.debug_permission }, { title: t(`${i18nPrefix}.whoCanDebug`), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
].map(({ title, key, value }) => ( ].map(({ title, key, value }) => (
<div key={key} className='flex flex-col items-start gap-1 self-stretch'> <div key={key} className='flex flex-col items-start gap-1 self-stretch'>
<div className='flex h-6 items-center gap-0.5'> <div className='flex h-6 items-center gap-0.5'>

@ -21,4 +21,6 @@
z-index: -1000 !important; z-index: -1000 !important;
} }
#workflow-container .react-flow {} #workflow-container .react-flow__attribution {
background: none !important;
}

Loading…
Cancel
Save