From 723b69cf8d64bd69045934d74163b0b68f2f3859 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 6 Jun 2025 16:15:37 +0800 Subject: [PATCH 01/11] chore: chart panel ui enhance (#20743) --- .../[appId]/overview/chartView.tsx | 32 ++++++++++--------- .../[appId]/overview/tracing/panel.tsx | 13 -------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx index 32822e3315..646c8bd93d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx @@ -47,22 +47,24 @@ export default function ChartView({ appId, headerRight }: IChartViewProps) { return (
-
-
- {t('appOverview.analysis.title')} - ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} - className='mt-0 !w-40' - onSelect={(item) => { - const id = item.value - const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1' - const name = item.name || t('appLog.filter.period.allTime') - onSelect({ value, name }) - }} - defaultValue={'2'} - /> +
+
{t('common.appMenus.overview')}
+
+
+ ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} + className='mt-0 !w-40' + onSelect={(item) => { + const id = item.value + const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1' + const name = item.name || t('appLog.filter.period.allTime') + onSelect({ value, name }) + }} + defaultValue={'2'} + /> +
+ {headerRight}
- {headerRight}
{!isWorkflow && (
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index bc85f3a734..76e90ecf19 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -23,19 +23,6 @@ import Divider from '@/app/components/base/divider' const I18N_PREFIX = 'app.tracing' -const Title = ({ - className, -}: { - className?: string -}) => { - const { t } = useTranslation() - - return ( -
- {t('common.appMenus.overview')} -
- ) -} const Panel: FC = () => { const { t } = useTranslation() const pathname = usePathname() From 37c3283450c9bec0d8b44d2335be85603d0f294c Mon Sep 17 00:00:00 2001 From: jefferyvvv <33647240+jefferyvvv@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:29:15 +0800 Subject: [PATCH 02/11] fix: opensearch vector search falls back to keyword search (#20723) Co-authored-by: wenjun.gu --- .../datasource/vdb/opensearch/opensearch_vector.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py index 6a6c2b73ef..0abb3c0077 100644 --- a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py +++ b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py @@ -184,7 +184,16 @@ class OpenSearchVector(BaseVector): } document_ids_filter = kwargs.get("document_ids_filter") if document_ids_filter: - query["query"] = {"terms": {"metadata.document_id": document_ids_filter}} + query["query"] = { + "script_score": { + "query": {"bool": {"filter": [{"terms": {Field.DOCUMENT_ID.value: document_ids_filter}}]}}, + "script": { + "source": "knn_score", + "lang": "knn", + "params": {"field": Field.VECTOR.value, "query_value": query_vector, "space_type": "l2"}, + }, + } + } try: response = self._client.search(index=self._collection_name.lower(), body=query) From 0c8447fd0e48851f862f050e951296ad39844071 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 6 Jun 2025 16:44:36 +0800 Subject: [PATCH 03/11] fix: missing bot name in orchestrate (#20747) --- web/app/components/base/chat/chat/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index c0842af0c4..801daa6589 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -303,7 +303,7 @@ const Chat: FC = ({ { !noChatInput && ( Date: Fri, 6 Jun 2025 21:03:59 +0800 Subject: [PATCH 04/11] feat(api): Adjust `WorkflowDraftVariable` and `WorkflowNodeExecutionModel` (#20746) - Add `node_execution_id` column to `WorkflowDraftVariable`, allowing efficient implementation of the "Reset to last run value" feature. - Add additional index for `WorkflowNodeExecutionModel` to improve the performance of last run lookup. Closes #20745. --- ...w_draft_varaibles_add_node_execution_id.py | 60 +++++++++++++ api/models/workflow.py | 87 +++++++++++++------ 2 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py diff --git a/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py b/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py new file mode 100644 index 0000000000..d7a5d116c9 --- /dev/null +++ b/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py @@ -0,0 +1,60 @@ +"""`workflow_draft_varaibles` add `node_execution_id` column, add an index for `workflow_node_executions`. + +Revision ID: 4474872b0ee6 +Revises: 2adcbe1f5dfb +Create Date: 2025-06-06 14:24:44.213018 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4474872b0ee6' +down_revision = '2adcbe1f5dfb' +branch_labels = None +depends_on = None + + +def upgrade(): + # `CREATE INDEX CONCURRENTLY` cannot run within a transaction, so use the `autocommit_block` + # context manager to wrap the index creation statement. + # Reference: + # + # - https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + # - https://alembic.sqlalchemy.org/en/latest/api/runtime.html#alembic.runtime.migration.MigrationContext.autocommit_block + with op.get_context().autocommit_block(): + op.create_index( + op.f('workflow_node_executions_tenant_id_idx'), + "workflow_node_executions", + ['tenant_id', 'workflow_id', 'node_id', sa.literal_column('created_at DESC')], + unique=False, + postgresql_concurrently=True, + ) + + with op.batch_alter_table('workflow_draft_variables', schema=None) as batch_op: + batch_op.add_column(sa.Column('node_execution_id', models.types.StringUUID(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + # `DROP INDEX CONCURRENTLY` cannot run within a transaction, so use the `autocommit_block` + # context manager to wrap the index creation statement. + # Reference: + # + # - https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + # - https://alembic.sqlalchemy.org/en/latest/api/runtime.html#alembic.runtime.migration.MigrationContext.autocommit_block + # `DROP INDEX CONCURRENTLY` cannot run within a transaction, so commit existing transactions first. + # Reference: + # + # https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + with op.get_context().autocommit_block(): + op.drop_index(op.f('workflow_node_executions_tenant_id_idx'), postgresql_concurrently=True) + + with op.batch_alter_table('workflow_draft_variables', schema=None) as batch_op: + batch_op.drop_column('node_execution_id') + + # ### end Alembic commands ### diff --git a/api/models/workflow.py b/api/models/workflow.py index e868fb77a7..2fff045543 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -16,8 +16,8 @@ if TYPE_CHECKING: from models.model import AppMode import sqlalchemy as sa -from sqlalchemy import UniqueConstraint, func -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import Index, PrimaryKeyConstraint, UniqueConstraint, func +from sqlalchemy.orm import Mapped, declared_attr, mapped_column from constants import DEFAULT_FILE_NUMBER_LIMITS, HIDDEN_VALUE from core.helper import encrypter @@ -590,28 +590,48 @@ class WorkflowNodeExecutionModel(Base): """ __tablename__ = "workflow_node_executions" - __table_args__ = ( - db.PrimaryKeyConstraint("id", name="workflow_node_execution_pkey"), - db.Index( - "workflow_node_execution_workflow_run_idx", - "tenant_id", - "app_id", - "workflow_id", - "triggered_from", - "workflow_run_id", - ), - db.Index( - "workflow_node_execution_node_run_idx", "tenant_id", "app_id", "workflow_id", "triggered_from", "node_id" - ), - db.Index( - "workflow_node_execution_id_idx", - "tenant_id", - "app_id", - "workflow_id", - "triggered_from", - "node_execution_id", - ), - ) + + @declared_attr + def __table_args__(cls): # noqa + return ( + PrimaryKeyConstraint("id", name="workflow_node_execution_pkey"), + Index( + "workflow_node_execution_workflow_run_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "workflow_run_id", + ), + Index( + "workflow_node_execution_node_run_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "node_id", + ), + Index( + "workflow_node_execution_id_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "node_execution_id", + ), + Index( + # The first argument is the index name, + # which we leave as `None`` to allow auto-generation by the ORM. + None, + cls.tenant_id, + cls.workflow_id, + cls.node_id, + # MyPy may flag the following line because it doesn't recognize that + # the `declared_attr` decorator passes the receiving class as the first + # argument to this method, allowing us to reference class attributes. + cls.created_at.desc(), # type: ignore + ), + ) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) tenant_id: Mapped[str] = mapped_column(StringUUID) @@ -885,14 +905,29 @@ class WorkflowDraftVariable(Base): selector: Mapped[str] = mapped_column(sa.String(255), nullable=False, name="selector") + # The data type of this variable's value value_type: Mapped[SegmentType] = mapped_column(EnumText(SegmentType, length=20)) - # JSON string + + # The variable's value serialized as a JSON string value: Mapped[str] = mapped_column(sa.Text, nullable=False, name="value") - # visible + # Controls whether the variable should be displayed in the variable inspection panel visible: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True) + + # Determines whether this variable can be modified by users editable: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=False) + # The `node_execution_id` field identifies the workflow node execution that created this variable. + # It corresponds to the `id` field in the `WorkflowNodeExecutionModel` model. + # + # This field is not `None` for system variables and node variables, and is `None` + # for conversation variables. + node_execution_id: Mapped[str | None] = mapped_column( + StringUUID, + nullable=True, + default=None, + ) + def get_selector(self) -> list[str]: selector = json.loads(self.selector) if not isinstance(selector, list): From e6e76852d5fd630890a28c7e11a76e35bf32ce27 Mon Sep 17 00:00:00 2001 From: Bharat Ramanathan Date: Sat, 7 Jun 2025 20:36:23 +0530 Subject: [PATCH 05/11] Add support for W&B dedicated cloud instances in Weave tracing integration (#20765) Co-authored-by: crazywoola <427733928@qq.com> --- api/core/ops/entities/config_entity.py | 9 +++++++++ api/core/ops/ops_trace_manager.py | 2 +- api/core/ops/weave_trace/weave_trace.py | 15 ++++++++++++--- .../overview/tracing/provider-config-modal.tsx | 8 ++++++++ .../[appId]/overview/tracing/type.ts | 1 + 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index f2d1bd305a..c988bf48d1 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -98,6 +98,7 @@ class WeaveConfig(BaseTracingConfig): entity: str | None = None project: str endpoint: str = "https://trace.wandb.ai" + host: str | None = None @field_validator("endpoint") @classmethod @@ -109,6 +110,14 @@ class WeaveConfig(BaseTracingConfig): return v + @field_validator("host") + @classmethod + def validate_host(cls, v, info: ValidationInfo): + if v is not None and v != "": + if not v.startswith(("https://", "http://")): + raise ValueError("host must start with https:// or http://") + return v + OPS_FILE_PATH = "ops_trace/" OPS_TRACE_FAILED_KEY = "FAILED_OPS_TRACE" diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index dc4cfc48db..e0dfe0c312 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -81,7 +81,7 @@ class OpsTraceProviderConfigMap(dict[str, dict[str, Any]]): return { "config_class": WeaveConfig, "secret_keys": ["api_key"], - "other_keys": ["project", "entity", "endpoint"], + "other_keys": ["project", "entity", "endpoint", "host"], "trace_instance": WeaveDataTrace, } diff --git a/api/core/ops/weave_trace/weave_trace.py b/api/core/ops/weave_trace/weave_trace.py index cfc8a505bb..3917348a91 100644 --- a/api/core/ops/weave_trace/weave_trace.py +++ b/api/core/ops/weave_trace/weave_trace.py @@ -40,9 +40,14 @@ class WeaveDataTrace(BaseTraceInstance): self.weave_api_key = weave_config.api_key self.project_name = weave_config.project self.entity = weave_config.entity + self.host = weave_config.host + + # Login with API key first, including host if provided + if self.host: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True, host=self.host) + else: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) - # Login with API key first - login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) if not login_status: logger.error("Failed to login to Weights & Biases with the provided API key") raise ValueError("Weave login failed") @@ -386,7 +391,11 @@ class WeaveDataTrace(BaseTraceInstance): def api_check(self): try: - login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) + if self.host: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True, host=self.host) + else: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) + if not login_status: raise ValueError("Weave login failed") else: diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index c0b52a9b10..b6c97add48 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -55,6 +55,7 @@ const weaveConfigTemplate = { entity: '', project: '', endpoint: '', + host: '', } const ProviderConfigModal: FC = ({ @@ -226,6 +227,13 @@ const ProviderConfigModal: FC = ({ onChange={handleConfigChange('endpoint')} placeholder={'https://trace.wandb.ai/'} /> + )} {type === TracingProvider.langSmith && ( diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts index 386c58974e..ed468caf65 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts @@ -29,4 +29,5 @@ export type WeaveConfig = { entity: string project: string endpoint: string + host: string } From 65c7c01d90e5af44d4c9ff41689a855a7a82c772 Mon Sep 17 00:00:00 2001 From: yihong Date: Sat, 7 Jun 2025 23:06:46 +0800 Subject: [PATCH 06/11] fix: clean up two unreachable code (#20773) Signed-off-by: yihong0618 --- api/core/rag/datasource/vdb/oracle/oraclevector.py | 1 - .../utils/dataset_retriever/dataset_multi_retriever_tool.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/api/core/rag/datasource/vdb/oracle/oraclevector.py b/api/core/rag/datasource/vdb/oracle/oraclevector.py index 6b9dd9c561..d1c8142b3d 100644 --- a/api/core/rag/datasource/vdb/oracle/oraclevector.py +++ b/api/core/rag/datasource/vdb/oracle/oraclevector.py @@ -303,7 +303,6 @@ class OracleVector(BaseVector): return docs else: return [Document(page_content="", metadata={})] - return [] def delete(self) -> None: with self._get_connection() as conn: diff --git a/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py b/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py index 93d3fcc49d..2cbc4b9821 100644 --- a/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py @@ -153,8 +153,6 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool): return str("\n".join(document_context_list)) return "" - raise RuntimeError("not segments found") - def _retriever( self, flask_app: Flask, From d6a8af03b4d2a56a30c6e42a2967347a6435c7ae Mon Sep 17 00:00:00 2001 From: NFish Date: Mon, 9 Jun 2025 15:44:49 +0800 Subject: [PATCH 07/11] Fix/add webapp no permission page (#20819) --- web/app/(shareLayout)/webapp-signin/page.tsx | 6 ++-- .../components/app/app-publisher/index.tsx | 6 ++-- web/app/components/base/app-unavailable.tsx | 2 +- .../base/chat/chat-with-history/index.tsx | 32 +++++++++++++++++-- .../base/chat/embedded-chatbot/index.tsx | 32 ++++++++++++++++--- .../share/text-generation/index.tsx | 23 +++++++++++-- web/app/components/share/utils.ts | 18 +---------- web/service/base.ts | 14 +++++--- 8 files changed, 95 insertions(+), 38 deletions(-) diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index c12fde38dd..07b7c88430 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -23,10 +23,12 @@ const WebSSOForm: FC = () => { const redirectUrl = searchParams.get('redirect_url') const tokenFromUrl = searchParams.get('web_sso_token') const message = searchParams.get('message') + const code = searchParams.get('code') const getSigninUrl = useCallback(() => { const params = new URLSearchParams(searchParams) params.delete('message') + params.delete('code') return `/webapp-signin?${params.toString()}` }, [searchParams]) @@ -85,8 +87,8 @@ const WebSSOForm: FC = () => { if (message) { return
- - {t('share.login.backToHome')} + + {code === '403' ? t('common.userProfile.logout') : t('share.login.backToHome')}
} if (!redirectUrl) { diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 5825bb72ee..1485964198 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -278,7 +278,7 @@ const AppPublisher = ({ onClick={() => { setShowAppAccessControl(true) }}> -
+
{appDetail?.access_mode === AccessMode.ORGANIZATION && <> @@ -288,7 +288,9 @@ const AppPublisher = ({ {appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && <> -

{t('app.accessControlDialog.accessItems.specific')}

+
+ {t('app.accessControlDialog.accessItems.specific')} +
} {appDetail?.access_mode === AccessMode.PUBLIC diff --git a/web/app/components/base/app-unavailable.tsx b/web/app/components/base/app-unavailable.tsx index 928c850262..c501d36118 100644 --- a/web/app/components/base/app-unavailable.tsx +++ b/web/app/components/base/app-unavailable.tsx @@ -21,7 +21,7 @@ const AppUnavailable: FC = ({ return (
-

{code}

diff --git a/web/app/components/base/chat/chat-with-history/index.tsx b/web/app/components/base/chat/chat-with-history/index.tsx index 1fd1383196..fe8e7b430d 100644 --- a/web/app/components/base/chat/chat-with-history/index.tsx +++ b/web/app/components/base/chat/chat-with-history/index.tsx @@ -1,5 +1,7 @@ +'use client' import type { FC } from 'react' import { + useCallback, useEffect, useState, } from 'react' @@ -17,10 +19,12 @@ import ChatWrapper from './chat-wrapper' import type { InstalledApp } from '@/models/explore' import Loading from '@/app/components/base/loading' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { checkOrSetAccessToken } from '@/app/components/share/utils' +import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import cn from '@/utils/classnames' import useDocumentTitle from '@/hooks/use-document-title' +import { useTranslation } from 'react-i18next' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' type ChatWithHistoryProps = { className?: string @@ -38,6 +42,7 @@ const ChatWithHistory: FC = ({ isMobile, themeBuilder, sidebarCollapseState, + isInstalledApp, } = useChatWithHistoryContext() const isSidebarCollapsed = sidebarCollapseState const customConfig = appData?.custom_config @@ -51,13 +56,34 @@ const ChatWithHistory: FC = ({ useDocumentTitle(site?.title || 'Chat') + const { t } = useTranslation() + const searchParams = useSearchParams() + const router = useRouter() + const pathname = usePathname() + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + if (appInfoLoading) { return ( ) } - if (!userCanAccess) - return + if (!userCanAccess) { + return
+ + {!isInstalledApp && {t('common.userProfile.logout')}} +
+ } if (appInfoError) { return ( diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index 002d142542..c54afd78ea 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -1,4 +1,6 @@ +'use client' import { + useCallback, useEffect, useState, } from 'react' @@ -12,7 +14,7 @@ import { useEmbeddedChatbot } from './hooks' import { isDify } from './utils' import { useThemeContext } from './theme/theme-context' import { CssTransform } from './theme/utils' -import { checkOrSetAccessToken } from '@/app/components/share/utils' +import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Loading from '@/app/components/base/loading' @@ -23,6 +25,7 @@ import DifyLogo from '@/app/components/base/logo/dify-logo' import cn from '@/utils/classnames' import useDocumentTitle from '@/hooks/use-document-title' import { useGlobalPublicStore } from '@/context/global-public-context' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' const Chatbot = () => { const { @@ -36,6 +39,7 @@ const Chatbot = () => { chatShouldReloadKey, handleNewConversation, themeBuilder, + isInstalledApp, } = useEmbeddedChatbotContext() const { t } = useTranslation() const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) @@ -51,6 +55,22 @@ const Chatbot = () => { useDocumentTitle(site?.title || 'Chat') + const searchParams = useSearchParams() + const router = useRouter() + const pathname = usePathname() + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + if (appInfoLoading) { return ( <> @@ -66,8 +86,12 @@ const Chatbot = () => { ) } - if (!userCanAccess) - return + if (!userCanAccess) { + return
+ + {!isInstalledApp && {t('common.userProfile.logout')}} +
+ } if (appInfoError) { return ( @@ -141,7 +165,6 @@ const EmbeddedChatbotWrapper = () => { appInfoError, appInfoLoading, appData, - accessMode, userCanAccess, appParams, appMeta, @@ -176,7 +199,6 @@ const EmbeddedChatbotWrapper = () => { return = ({
) + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + if (!appId || !siteInfo || !promptConfig || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission))) { return (
) } - if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) - return + if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) { + return
+ + {!isInstalledApp && {t('common.userProfile.logout')}} +
+ } return (
{ - const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - - const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) - let accessTokenJson = getInitialTokenV2() - try { - accessTokenJson = JSON.parse(accessToken) - if (isTokenV1(accessTokenJson)) - accessTokenJson = getInitialTokenV2() - } - catch { - - } - - localStorage.removeItem(CONVERSATION_ID_INFO) + localStorage.removeItem('token') localStorage.removeItem('webapp_access_token') - - delete accessTokenJson[sharedToken] - localStorage.setItem('token', JSON.stringify(accessTokenJson)) } diff --git a/web/service/base.ts b/web/service/base.ts index c3cafe600b..ba398c07a6 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -108,12 +108,13 @@ function unicodeToChar(text: string) { }) } -function requiredWebSSOLogin(message?: string) { - removeAccessToken() +function requiredWebSSOLogin(message?: string, code?: number) { const params = new URLSearchParams() params.append('redirect_url', globalThis.location.pathname) if (message) params.append('message', message) + if (code) + params.append('code', String(code)) globalThis.location.href = `/webapp-signin?${params.toString()}` } @@ -403,10 +404,12 @@ export const ssePost = async ( res.json().then((data: any) => { if (isPublicAPI) { if (data.code === 'web_app_access_denied') - requiredWebSSOLogin(data.message) + requiredWebSSOLogin(data.message, 403) - if (data.code === 'web_sso_auth_required') + if (data.code === 'web_sso_auth_required') { + removeAccessToken() requiredWebSSOLogin() + } if (data.code === 'unauthorized') { removeAccessToken() @@ -484,10 +487,11 @@ export const request = async(url: string, options = {}, otherOptions?: IOther const { code, message } = errRespData // webapp sso if (code === 'web_app_access_denied') { - requiredWebSSOLogin(message) + requiredWebSSOLogin(message, 403) return Promise.reject(err) } if (code === 'web_sso_auth_required') { + removeAccessToken() requiredWebSSOLogin() return Promise.reject(err) } From ab62a9662c9d56048d6414339c951fd9182bce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Mon, 9 Jun 2025 16:09:27 +0800 Subject: [PATCH 08/11] fix: some dark mode display incorrect (#20788) --- web/app/components/base/chat/chat/chat-input-area/index.tsx | 2 +- web/app/components/base/markdown-blocks/think-block.tsx | 4 ++-- .../nodes/_base/components/variable/var-reference-vars.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/chat/chat/chat-input-area/index.tsx b/web/app/components/base/chat/chat/chat-input-area/index.tsx index 52490e4024..cbfa3168e9 100644 --- a/web/app/components/base/chat/chat/chat-input-area/index.tsx +++ b/web/app/components/base/chat/chat/chat-input-area/index.tsx @@ -192,7 +192,7 @@ const ChatInputArea = ({