Add always_new_chat option to apps and sites

Introduces a new boolean field 'always_new_chat' (default true) to both apps and sites in the backend and frontend. This option allows configuration to always start a new chat session, ignoring previous conversations. Includes database migration, API/controller updates, model changes, UI controls, and i18n support.
pull/22059/head
Kalo Chin 11 months ago
parent d61ea5a2de
commit b8b5acc1f0

@ -151,6 +151,7 @@ class AppApi(Resource):
parser.add_argument("icon", type=str, location="json")
parser.add_argument("icon_background", type=str, location="json")
parser.add_argument("use_icon_as_answer_icon", type=bool, location="json")
parser.add_argument("always_new_chat", type=bool, location="json")
args = parser.parse_args()
app_service = AppService()

@ -34,6 +34,7 @@ def parse_app_site_args():
parser.add_argument("prompt_public", type=bool, required=False, location="json")
parser.add_argument("show_workflow_steps", type=bool, required=False, location="json")
parser.add_argument("use_icon_as_answer_icon", type=bool, required=False, location="json")
parser.add_argument("always_new_chat", type=bool, required=False, location="json")
return parser.parse_args()
@ -71,6 +72,7 @@ class AppSite(Resource):
"prompt_public",
"show_workflow_steps",
"use_icon_as_answer_icon",
"always_new_chat",
]:
value = args.get(attr_name)
if value is not None:

@ -40,6 +40,7 @@ class AppSiteApi(WebApiResource):
"prompt_public": fields.Boolean,
"show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
}
app_fields = {

@ -59,6 +59,7 @@ app_detail_fields = {
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"tracing": fields.Raw,
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
@ -94,6 +95,7 @@ app_partial_fields = {
"model_config": fields.Nested(model_config_partial_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
@ -147,6 +149,7 @@ site_fields = {
"app_base_url": fields.String,
"show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
@ -175,6 +178,7 @@ app_detail_fields_with_site = {
"site": fields.Nested(site_fields),
"api_base_url": fields.String,
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
@ -201,6 +205,7 @@ app_site_fields = {
"prompt_public": fields.Boolean,
"show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
"always_new_chat": fields.Boolean,
}
leaked_dependency_fields = {"type": fields.String, "value": fields.Raw, "current_identifier": fields.String}

@ -0,0 +1,24 @@
"""add always_new_chat boolean field to apps and sites (default true)
Revision ID: 0001_add_always_new_chat
Revises:
Create Date: 2025-07-08 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd57ba9ebb254'
down_revision = '0ab65e1cc7fa'
branch_labels = None
depends_on = None
def upgrade():
for table in ('apps', 'sites'):
op.add_column(table, sa.Column('always_new_chat', sa.Boolean(), nullable=False, server_default=sa.text('true')))
def downgrade():
for table in ('apps', 'sites'):
op.drop_column(table, 'always_new_chat')

@ -100,6 +100,7 @@ class App(Base):
updated_by = db.Column(StringUUID, nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp())
use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
always_new_chat = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
@property
def desc_or_prompt(self):
@ -1478,6 +1479,7 @@ class Site(Base):
privacy_policy = db.Column(db.String(255))
show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
always_new_chat = db.Column(db.Boolean, nullable=False, server_default=db.text("true"))
_custom_disclaimer: Mapped[str] = mapped_column("custom_disclaimer", sa.TEXT, default="")
customize_domain = db.Column(db.String(255))
customize_token_strategy = db.Column(db.String(255), nullable=False)

@ -235,6 +235,7 @@ class AppService:
app.icon = args.get("icon")
app.icon_background = args.get("icon_background")
app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)
app.always_new_chat = args.get("always_new_chat", True)
app.updated_by = current_user.id
app.updated_at = datetime.now(UTC).replace(tzinfo=None)
db.session.commit()

@ -51,6 +51,7 @@ export type ConfigParams = {
icon_background?: string
show_workflow_steps: boolean
use_icon_as_answer_icon: boolean
always_new_chat: boolean
enable_sso?: boolean
}
@ -80,6 +81,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
default_language,
show_workflow_steps,
use_icon_as_answer_icon,
always_new_chat,
} = appInfo.site
const [inputInfo, setInputInfo] = useState({
title,
@ -92,6 +94,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
customDisclaimer: custom_disclaimer,
show_workflow_steps,
use_icon_as_answer_icon,
always_new_chat,
enable_sso: appInfo.enable_sso,
})
const [language, setLanguage] = useState(default_language)
@ -128,13 +131,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({
customDisclaimer: custom_disclaimer,
show_workflow_steps,
use_icon_as_answer_icon,
always_new_chat,
enable_sso: appInfo.enable_sso,
})
setLanguage(default_language)
setAppIcon(icon_type === 'image'
? { type: 'image', url: icon_url!, fileId: icon }
: { type: 'emoji', icon, background: icon_background! })
}, [appInfo, chat_color_theme, chat_color_theme_inverted, copyright, custom_disclaimer, default_language, description, icon, icon_background, icon_type, icon_url, privacy_policy, show_workflow_steps, title, use_icon_as_answer_icon])
}, [appInfo, chat_color_theme, chat_color_theme_inverted, copyright, custom_disclaimer, default_language, description, icon, icon_background, icon_type, icon_url, privacy_policy, show_workflow_steps, title, use_icon_as_answer_icon, always_new_chat])
const onHide = () => {
onClose()
@ -196,6 +200,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined,
show_workflow_steps: inputInfo.show_workflow_steps,
use_icon_as_answer_icon: inputInfo.use_icon_as_answer_icon,
always_new_chat: inputInfo.always_new_chat,
enable_sso: inputInfo.enable_sso,
}
await onSave?.(params)
@ -291,6 +296,19 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<p className='body-xs-regular pb-0.5 text-text-tertiary'>{t('app.answerIcon.description')}</p>
</div>
)}
{/* always new chat */}
{isChat && (
<div className='w-full'>
<div className='flex items-center justify-between'>
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t('app.alwaysNewChat.title')}</div>
<Switch
defaultValue={inputInfo.always_new_chat}
onChange={v => setInputInfo({ ...inputInfo, always_new_chat: v })}
/>
</div>
<p className='body-xs-regular pb-0.5 text-text-tertiary'>{t('app.alwaysNewChat.description')}</p>
</div>
)}
{/* language */}
<div className='flex items-center'>
<div className={cn('system-sm-semibold grow py-1 text-text-secondary')}>{t(`${prefixSettings}.language`)}</div>

@ -142,7 +142,12 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {
defaultValue: {},
})
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId])
const [userSelectedConversation, setUserSelectedConversation] = useState(false)
const currentConversationId = useMemo(() => {
if (appData?.site?.always_new_chat && !userSelectedConversation)
return ''
return conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || ''
}, [appId, conversationIdInfo, userId, appData, userSelectedConversation])
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
if (appId) {
let prevValue = conversationIdInfo?.[appId || '']
@ -373,6 +378,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}, [setShowNewConversationItemInList, checkInputsRequired])
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop })
const handleChangeConversation = useCallback((conversationId: string) => {
setUserSelectedConversation(!!conversationId)
currentChatInstanceRef.current.handleStop()
setNewConversationId('')
handleConversationIdInfoChange(conversationId)

@ -120,8 +120,11 @@ export const useEmbeddedChatbot = () => {
defaultValue: {},
})
const allowResetChat = !conversationId
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '',
[appId, conversationIdInfo, userId, conversationId])
const currentConversationId = useMemo(() => {
if (appData?.site?.always_new_chat && !userSelectedConversation)
return ''
return conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || ''
}, [appId, conversationIdInfo, userId, conversationId, appData, userSelectedConversation])
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
if (appId) {
let prevValue = conversationIdInfo?.[appId || '']
@ -161,6 +164,7 @@ export const useEmbeddedChatbot = () => {
)
const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false)
const [userSelectedConversation, setUserSelectedConversation] = useState(false)
const pinnedConversationList = useMemo(() => {
return appPinnedConversationData?.data || []
@ -354,6 +358,7 @@ export const useEmbeddedChatbot = () => {
}, [setShowNewConversationItemInList, checkInputsRequired])
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop })
const handleChangeConversation = useCallback((conversationId: string) => {
setUserSelectedConversation(!!conversationId)
currentChatInstanceRef.current.handleStop()
setNewConversationId('')
handleConversationIdInfoChange(conversationId)

@ -117,6 +117,10 @@ const translation = {
description: 'Whether to use the web app icon to replace 🤖 in the shared application',
descriptionInExplore: 'Whether to use the web app icon to replace 🤖 in Explore',
},
alwaysNewChat: {
title: 'Always start a new chat',
description: 'Ignore previous conversation and open a new one when users visit the web app',
},
switch: 'Switch to Workflow Orchestrate',
switchTipStart: 'A new app copy will be created for you, and the new copy will switch to Workflow Orchestrate. The new copy will ',
switchTip: 'not allow',

@ -26,6 +26,7 @@ export type SiteInfo = {
custom_disclaimer?: string
show_workflow_steps?: boolean
use_icon_as_answer_icon?: boolean
always_new_chat?: boolean
}
export type AppMeta = {

Loading…
Cancel
Save