mirror of
https://github.com/langgenius/dify.git
synced 2026-05-02 15:17:39 +08:00
Merge branch 'fix/async-window-open' into deploy/dev
This commit is contained in:
commit
eb525b697f
@ -483,7 +483,7 @@ class RetrievalService:
|
|||||||
DocumentSegment.status == "completed",
|
DocumentSegment.status == "completed",
|
||||||
DocumentSegment.id == segment_id,
|
DocumentSegment.id == segment_id,
|
||||||
)
|
)
|
||||||
segment = db.session.scalar(document_segment_stmt)
|
segment = session.scalar(document_segment_stmt)
|
||||||
if segment:
|
if segment:
|
||||||
segment_file_map[segment.id] = [attachment_info]
|
segment_file_map[segment.id] = [attachment_info]
|
||||||
else:
|
else:
|
||||||
@ -496,7 +496,7 @@ class RetrievalService:
|
|||||||
DocumentSegment.status == "completed",
|
DocumentSegment.status == "completed",
|
||||||
DocumentSegment.index_node_id == index_node_id,
|
DocumentSegment.index_node_id == index_node_id,
|
||||||
)
|
)
|
||||||
segment = db.session.scalar(document_segment_stmt)
|
segment = session.scalar(document_segment_stmt)
|
||||||
|
|
||||||
if not segment:
|
if not segment:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -334,6 +334,7 @@ class LLMNode(Node[LLMNodeData]):
|
|||||||
inputs=node_inputs,
|
inputs=node_inputs,
|
||||||
process_data=process_data,
|
process_data=process_data,
|
||||||
error_type=type(e).__name__,
|
error_type=type(e).__name__,
|
||||||
|
llm_usage=usage,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -344,6 +345,8 @@ class LLMNode(Node[LLMNodeData]):
|
|||||||
error=str(e),
|
error=str(e),
|
||||||
inputs=node_inputs,
|
inputs=node_inputs,
|
||||||
process_data=process_data,
|
process_data=process_data,
|
||||||
|
error_type=type(e).__name__,
|
||||||
|
llm_usage=usage,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -221,6 +221,7 @@ class QuestionClassifierNode(Node[QuestionClassifierNodeData]):
|
|||||||
status=WorkflowNodeExecutionStatus.FAILED,
|
status=WorkflowNodeExecutionStatus.FAILED,
|
||||||
inputs=variables,
|
inputs=variables,
|
||||||
error=str(e),
|
error=str(e),
|
||||||
|
error_type=type(e).__name__,
|
||||||
metadata={
|
metadata={
|
||||||
WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens,
|
WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens,
|
||||||
WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price,
|
WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price,
|
||||||
|
|||||||
@ -78,7 +78,7 @@ class Dataset(Base):
|
|||||||
pipeline_id = mapped_column(StringUUID, nullable=True)
|
pipeline_id = mapped_column(StringUUID, nullable=True)
|
||||||
chunk_structure = mapped_column(sa.String(255), nullable=True)
|
chunk_structure = mapped_column(sa.String(255), nullable=True)
|
||||||
enable_api = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("true"))
|
enable_api = mapped_column(sa.Boolean, nullable=False, server_default=sa.text("true"))
|
||||||
is_multimodal = mapped_column(sa.Boolean, nullable=False, server_default=db.text("false"))
|
is_multimodal = mapped_column(sa.Boolean, default=False, nullable=False, server_default=db.text("false"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_documents(self):
|
def total_documents(self):
|
||||||
|
|||||||
@ -259,7 +259,7 @@ class App(Base):
|
|||||||
provider_id = tool.get("provider_id", "")
|
provider_id = tool.get("provider_id", "")
|
||||||
|
|
||||||
if provider_type == ToolProviderType.API:
|
if provider_type == ToolProviderType.API:
|
||||||
if uuid.UUID(provider_id) not in existing_api_providers:
|
if provider_id not in existing_api_providers:
|
||||||
deleted_tools.append(
|
deleted_tools.append(
|
||||||
{
|
{
|
||||||
"type": ToolProviderType.API,
|
"type": ToolProviderType.API,
|
||||||
|
|||||||
@ -907,19 +907,29 @@ class WorkflowNodeExecutionModel(Base): # This model is expected to have `offlo
|
|||||||
@property
|
@property
|
||||||
def extras(self) -> dict[str, Any]:
|
def extras(self) -> dict[str, Any]:
|
||||||
from core.tools.tool_manager import ToolManager
|
from core.tools.tool_manager import ToolManager
|
||||||
|
from core.trigger.trigger_manager import TriggerManager
|
||||||
|
|
||||||
extras: dict[str, Any] = {}
|
extras: dict[str, Any] = {}
|
||||||
if self.execution_metadata_dict:
|
execution_metadata = self.execution_metadata_dict
|
||||||
if self.node_type == NodeType.TOOL and "tool_info" in self.execution_metadata_dict:
|
if execution_metadata:
|
||||||
tool_info: dict[str, Any] = self.execution_metadata_dict["tool_info"]
|
if self.node_type == NodeType.TOOL and "tool_info" in execution_metadata:
|
||||||
|
tool_info: dict[str, Any] = execution_metadata["tool_info"]
|
||||||
extras["icon"] = ToolManager.get_tool_icon(
|
extras["icon"] = ToolManager.get_tool_icon(
|
||||||
tenant_id=self.tenant_id,
|
tenant_id=self.tenant_id,
|
||||||
provider_type=tool_info["provider_type"],
|
provider_type=tool_info["provider_type"],
|
||||||
provider_id=tool_info["provider_id"],
|
provider_id=tool_info["provider_id"],
|
||||||
)
|
)
|
||||||
elif self.node_type == NodeType.DATASOURCE and "datasource_info" in self.execution_metadata_dict:
|
elif self.node_type == NodeType.DATASOURCE and "datasource_info" in execution_metadata:
|
||||||
datasource_info = self.execution_metadata_dict["datasource_info"]
|
datasource_info = execution_metadata["datasource_info"]
|
||||||
extras["icon"] = datasource_info.get("icon")
|
extras["icon"] = datasource_info.get("icon")
|
||||||
|
elif self.node_type == NodeType.TRIGGER_PLUGIN and "trigger_info" in execution_metadata:
|
||||||
|
trigger_info = execution_metadata["trigger_info"] or {}
|
||||||
|
provider_id = trigger_info.get("provider_id")
|
||||||
|
if provider_id:
|
||||||
|
extras["icon"] = TriggerManager.get_trigger_plugin_icon(
|
||||||
|
tenant_id=self.tenant_id,
|
||||||
|
provider_id=provider_id,
|
||||||
|
)
|
||||||
return extras
|
return extras
|
||||||
|
|
||||||
def _get_offload_by_type(self, type_: ExecutionOffLoadType) -> Optional["WorkflowNodeExecutionOffload"]:
|
def _get_offload_by_type(self, type_: ExecutionOffLoadType) -> Optional["WorkflowNodeExecutionOffload"]:
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
import { useKeyPress } from 'ahooks'
|
import { useKeyPress } from 'ahooks'
|
||||||
import Divider from '../../base/divider'
|
import Divider from '../../base/divider'
|
||||||
import Loading from '../../base/loading'
|
import Loading from '../../base/loading'
|
||||||
import Toast from '../../base/toast'
|
|
||||||
import Tooltip from '../../base/tooltip'
|
import Tooltip from '../../base/tooltip'
|
||||||
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../../workflow/utils'
|
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../../workflow/utils'
|
||||||
import AccessControl from '../app-access-control'
|
import AccessControl from '../app-access-control'
|
||||||
@ -50,6 +49,7 @@ import { AppModeEnum } from '@/types/app'
|
|||||||
import type { PublishWorkflowParams } from '@/types/workflow'
|
import type { PublishWorkflowParams } from '@/types/workflow'
|
||||||
import { basePath } from '@/utils/var'
|
import { basePath } from '@/utils/var'
|
||||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||||
|
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||||
|
|
||||||
const ACCESS_MODE_MAP: Record<AccessMode, { label: string, icon: React.ElementType }> = {
|
const ACCESS_MODE_MAP: Record<AccessMode, { label: string, icon: React.ElementType }> = {
|
||||||
[AccessMode.ORGANIZATION]: {
|
[AccessMode.ORGANIZATION]: {
|
||||||
@ -216,18 +216,23 @@ const AppPublisher = ({
|
|||||||
setPublished(false)
|
setPublished(false)
|
||||||
}, [disabled, onToggle, open])
|
}, [disabled, onToggle, open])
|
||||||
|
|
||||||
const handleOpenInExplore = useCallback(async () => {
|
const { openAsync } = useAsyncWindowOpen()
|
||||||
try {
|
|
||||||
const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {}
|
const handleOpenInExplore = useCallback(() => {
|
||||||
if (installed_apps?.length > 0)
|
if (!appDetail?.id) return
|
||||||
window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
|
|
||||||
else
|
openAsync(
|
||||||
|
async () => {
|
||||||
|
const { installed_apps }: { installed_apps?: { id: string }[] } = await fetchInstalledAppList(appDetail.id) || {}
|
||||||
|
if (installed_apps && installed_apps.length > 0)
|
||||||
|
return `${basePath}/explore/installed/${installed_apps[0].id}`
|
||||||
throw new Error('No app found in Explore')
|
throw new Error('No app found in Explore')
|
||||||
}
|
},
|
||||||
catch (e: any) {
|
{
|
||||||
Toast.notify({ type: 'error', message: `${e.message || e}` })
|
errorMessage: 'Failed to open app in Explore',
|
||||||
}
|
},
|
||||||
}, [appDetail?.id])
|
)
|
||||||
|
}, [appDetail?.id, openAsync])
|
||||||
|
|
||||||
const handleAccessControlUpdate = useCallback(async () => {
|
const handleAccessControlUpdate = useCallback(async () => {
|
||||||
if (!appDetail)
|
if (!appDetail)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
|
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { type App, AppModeEnum } from '@/types/app'
|
import { type App, AppModeEnum } from '@/types/app'
|
||||||
import Toast, { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
|
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
|
||||||
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
|
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
@ -31,6 +31,7 @@ import { AccessMode } from '@/models/access-control'
|
|||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import { formatTime } from '@/utils/time'
|
import { formatTime } from '@/utils/time'
|
||||||
import { useGetUserCanAccessApp } from '@/service/access-control'
|
import { useGetUserCanAccessApp } from '@/service/access-control'
|
||||||
|
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
const EditAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), {
|
const EditAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), {
|
||||||
@ -242,20 +243,24 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setShowAccessControl(true)
|
setShowAccessControl(true)
|
||||||
}
|
}
|
||||||
const onClickInstalledApp = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
const { openAsync } = useAsyncWindowOpen()
|
||||||
|
|
||||||
|
const onClickInstalledApp = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
props.onClick?.()
|
props.onClick?.()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
try {
|
|
||||||
const { installed_apps }: any = await fetchInstalledAppList(app.id) || {}
|
openAsync(
|
||||||
if (installed_apps?.length > 0)
|
async () => {
|
||||||
window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
|
const { installed_apps }: { installed_apps?: { id: string }[] } = await fetchInstalledAppList(app.id) || {}
|
||||||
else
|
if (installed_apps && installed_apps.length > 0)
|
||||||
|
return `${basePath}/explore/installed/${installed_apps[0].id}`
|
||||||
throw new Error('No app found in Explore')
|
throw new Error('No app found in Explore')
|
||||||
}
|
},
|
||||||
catch (e: any) {
|
{
|
||||||
Toast.notify({ type: 'error', message: `${e.message || e}` })
|
errorMessage: 'Failed to open app in Explore',
|
||||||
}
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full flex-col py-1" onMouseLeave={onMouseLeave}>
|
<div className="relative flex w-full flex-col py-1" onMouseLeave={onMouseLeave}>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import Toast from '../../../../base/toast'
|
|||||||
import { PlanRange } from '../../plan-switcher/plan-range-switcher'
|
import { PlanRange } from '../../plan-switcher/plan-range-switcher'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { fetchBillingUrl, fetchSubscriptionUrls } from '@/service/billing'
|
import { fetchBillingUrl, fetchSubscriptionUrls } from '@/service/billing'
|
||||||
|
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||||
import List from './list'
|
import List from './list'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import { Professional, Sandbox, Team } from '../../assets'
|
import { Professional, Sandbox, Team } from '../../assets'
|
||||||
@ -54,6 +55,8 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
|||||||
})[plan]
|
})[plan]
|
||||||
}, [isCurrent, plan, t])
|
}, [isCurrent, plan, t])
|
||||||
|
|
||||||
|
const { openAsync } = useAsyncWindowOpen()
|
||||||
|
|
||||||
const handleGetPayUrl = async () => {
|
const handleGetPayUrl = async () => {
|
||||||
if (loading)
|
if (loading)
|
||||||
return
|
return
|
||||||
@ -72,8 +75,13 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
if (isCurrentPaidPlan) {
|
if (isCurrentPaidPlan) {
|
||||||
const res = await fetchBillingUrl()
|
await openAsync(
|
||||||
window.open(res.url, '_blank')
|
() => fetchBillingUrl().then(res => res.url),
|
||||||
|
{
|
||||||
|
errorMessage: 'Failed to open billing page',
|
||||||
|
windowFeatures: 'noopener,noreferrer',
|
||||||
|
},
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
72
web/hooks/use-async-window-open.ts
Normal file
72
web/hooks/use-async-window-open.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
|
export type AsyncWindowOpenOptions = {
|
||||||
|
successMessage?: string
|
||||||
|
errorMessage?: string
|
||||||
|
windowFeatures?: string
|
||||||
|
onError?: (error: any) => void
|
||||||
|
onSuccess?: (url: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAsyncWindowOpen = () => {
|
||||||
|
const openAsync = useCallback(async (
|
||||||
|
fetchUrl: () => Promise<string>,
|
||||||
|
options: AsyncWindowOpenOptions = {},
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
successMessage,
|
||||||
|
errorMessage = 'Failed to open page',
|
||||||
|
windowFeatures = 'noopener,noreferrer',
|
||||||
|
onError,
|
||||||
|
onSuccess,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const newWindow = window.open('', '_blank', windowFeatures)
|
||||||
|
|
||||||
|
if (!newWindow) {
|
||||||
|
const error = new Error('Popup blocked by browser')
|
||||||
|
onError?.(error)
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Popup blocked. Please allow popups for this site.',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = await fetchUrl()
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
newWindow.location.href = url
|
||||||
|
onSuccess?.(url)
|
||||||
|
|
||||||
|
if (successMessage) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'success',
|
||||||
|
message: successMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newWindow.close()
|
||||||
|
const error = new Error('Invalid URL received')
|
||||||
|
onError?.(error)
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
newWindow.close()
|
||||||
|
onError?.(error)
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { openAsync }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user