Browse Source

feat: allow users to use the app icon as the answer icon (#7888)

Co-authored-by: crazywoola <427733928@qq.com>
tags/0.7.3
kurokobo 1 year ago
parent
commit
80aa7c4019
No account linked to committer's email address

+ 1
- 0
api/controllers/console/app/app.py View File

parser.add_argument("icon", type=str, location="json") parser.add_argument("icon", type=str, location="json")
parser.add_argument("icon_background", type=str, location="json") parser.add_argument("icon_background", type=str, location="json")
parser.add_argument("max_active_requests", type=int, location="json") parser.add_argument("max_active_requests", type=int, location="json")
parser.add_argument("use_icon_as_answer_icon", type=bool, location="json")
args = parser.parse_args() args = parser.parse_args()


app_service = AppService() app_service = AppService()

+ 2
- 0
api/controllers/console/app/site.py View File

) )
parser.add_argument("prompt_public", type=bool, required=False, location="json") 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("show_workflow_steps", type=bool, required=False, location="json")
parser.add_argument("use_icon_as_answer_icon", type=bool, required=False, location="json")
return parser.parse_args() return parser.parse_args()




"customize_token_strategy", "customize_token_strategy",
"prompt_public", "prompt_public",
"show_workflow_steps", "show_workflow_steps",
"use_icon_as_answer_icon",
]: ]:
value = args.get(attr_name) value = args.get(attr_name)
if value is not None: if value is not None:

+ 1
- 0
api/controllers/web/site.py View File

"default_language": fields.String, "default_language": fields.String,
"prompt_public": fields.Boolean, "prompt_public": fields.Boolean,
"show_workflow_steps": fields.Boolean, "show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
} }


app_fields = { app_fields = {

+ 5
- 0
api/fields/app_fields.py View File

"model_config": fields.Nested(model_config_fields, attribute="app_model_config", allow_null=True), "model_config": fields.Nested(model_config_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True), "workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"tracing": fields.Raw, "tracing": fields.Raw,
"use_icon_as_answer_icon": fields.Boolean,
"created_by": fields.String, "created_by": fields.String,
"created_at": TimestampField, "created_at": TimestampField,
"updated_by": fields.String, "updated_by": fields.String,
"icon_url": AppIconUrlField, "icon_url": AppIconUrlField,
"model_config": fields.Nested(model_config_partial_fields, attribute="app_model_config", allow_null=True), "model_config": fields.Nested(model_config_partial_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True), "workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"use_icon_as_answer_icon": fields.Boolean,
"created_by": fields.String, "created_by": fields.String,
"created_at": TimestampField, "created_at": TimestampField,
"updated_by": fields.String, "updated_by": fields.String,
"prompt_public": fields.Boolean, "prompt_public": fields.Boolean,
"app_base_url": fields.String, "app_base_url": fields.String,
"show_workflow_steps": fields.Boolean, "show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
"created_by": fields.String, "created_by": fields.String,
"created_at": TimestampField, "created_at": TimestampField,
"updated_by": fields.String, "updated_by": fields.String,
"workflow": fields.Nested(workflow_partial_fields, allow_null=True), "workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"site": fields.Nested(site_fields), "site": fields.Nested(site_fields),
"api_base_url": fields.String, "api_base_url": fields.String,
"use_icon_as_answer_icon": fields.Boolean,
"created_by": fields.String, "created_by": fields.String,
"created_at": TimestampField, "created_at": TimestampField,
"updated_by": fields.String, "updated_by": fields.String,
"customize_token_strategy": fields.String, "customize_token_strategy": fields.String,
"prompt_public": fields.Boolean, "prompt_public": fields.Boolean,
"show_workflow_steps": fields.Boolean, "show_workflow_steps": fields.Boolean,
"use_icon_as_answer_icon": fields.Boolean,
} }

+ 1
- 0
api/fields/installed_app_fields.py View File

"icon": fields.String, "icon": fields.String,
"icon_background": fields.String, "icon_background": fields.String,
"icon_url": AppIconUrlField, "icon_url": AppIconUrlField,
"use_icon_as_answer_icon": fields.Boolean,
} }


installed_app_fields = { installed_app_fields = {

+ 45
- 0
api/migrations/versions/2024_09_01_1255-030f4915f36a_add_use_icon_as_answer_icon_fields_for_.py View File

"""add use_icon_as_answer_icon fields for app and site

Revision ID: 030f4915f36a
Revises: d0187d6a88dd
Create Date: 2024-09-01 12:55:45.129687

"""

import sqlalchemy as sa
from alembic import op

import models as models

# revision identifiers, used by Alembic.
revision = "030f4915f36a"
down_revision = "d0187d6a88dd"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("apps", schema=None) as batch_op:
batch_op.add_column(
sa.Column("use_icon_as_answer_icon", sa.Boolean(), server_default=sa.text("false"), nullable=False)
)

with op.batch_alter_table("sites", schema=None) as batch_op:
batch_op.add_column(
sa.Column("use_icon_as_answer_icon", sa.Boolean(), server_default=sa.text("false"), nullable=False)
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###

with op.batch_alter_table("sites", schema=None) as batch_op:
batch_op.drop_column("use_icon_as_answer_icon")

with op.batch_alter_table("apps", schema=None) as batch_op:
batch_op.drop_column("use_icon_as_answer_icon")

# ### end Alembic commands ###

+ 2
- 0
api/models/model.py View File

created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
updated_by = db.Column(StringUUID, nullable=True) updated_by = db.Column(StringUUID, nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
use_icon_as_answer_icon = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))


@property @property
def desc_or_prompt(self): def desc_or_prompt(self):
copyright = db.Column(db.String(255)) copyright = db.Column(db.String(255))
privacy_policy = db.Column(db.String(255)) privacy_policy = db.Column(db.String(255))
show_workflow_steps = db.Column(db.Boolean, nullable=False, server_default=db.text('true')) 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"))
custom_disclaimer = db.Column(db.String(255), nullable=True) custom_disclaimer = db.Column(db.String(255), nullable=True)
customize_domain = db.Column(db.String(255)) customize_domain = db.Column(db.String(255))
customize_token_strategy = db.Column(db.String(255), nullable=False) customize_token_strategy = db.Column(db.String(255), nullable=False)

+ 12
- 0
api/services/app_dsl_service.py View File

icon_background = ( icon_background = (
args.get("icon_background") if args.get("icon_background") else app_data.get("icon_background") args.get("icon_background") if args.get("icon_background") else app_data.get("icon_background")
) )
use_icon_as_answer_icon = app_data.get("use_icon_as_answer_icon", False)


# import dsl and create app # import dsl and create app
app_mode = AppMode.value_of(app_data.get("mode")) app_mode = AppMode.value_of(app_data.get("mode"))
icon_type=icon_type, icon_type=icon_type,
icon=icon, icon=icon,
icon_background=icon_background, icon_background=icon_background,
use_icon_as_answer_icon=use_icon_as_answer_icon,
) )
elif app_mode in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.COMPLETION]: elif app_mode in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.COMPLETION]:
app = cls._import_and_create_new_model_config_based_app( app = cls._import_and_create_new_model_config_based_app(
icon_type=icon_type, icon_type=icon_type,
icon=icon, icon=icon,
icon_background=icon_background, icon_background=icon_background,
use_icon_as_answer_icon=use_icon_as_answer_icon,
) )
else: else:
raise ValueError("Invalid app mode") raise ValueError("Invalid app mode")
"icon": "🤖" if app_model.icon_type == "image" else app_model.icon, "icon": "🤖" if app_model.icon_type == "image" else app_model.icon,
"icon_background": "#FFEAD5" if app_model.icon_type == "image" else app_model.icon_background, "icon_background": "#FFEAD5" if app_model.icon_type == "image" else app_model.icon_background,
"description": app_model.description, "description": app_model.description,
"use_icon_as_answer_icon": app_model.use_icon_as_answer_icon,
}, },
} }


icon_type: str, icon_type: str,
icon: str, icon: str,
icon_background: str, icon_background: str,
use_icon_as_answer_icon: bool,
) -> App: ) -> App:
""" """
Import app dsl and create new workflow based app Import app dsl and create new workflow based app
:param icon_type: app icon type, "emoji" or "image" :param icon_type: app icon type, "emoji" or "image"
:param icon: app icon :param icon: app icon
:param icon_background: app icon background :param icon_background: app icon background
:param use_icon_as_answer_icon: use app icon as answer icon
""" """
if not workflow_data: if not workflow_data:
raise ValueError("Missing workflow in data argument " "when app mode is advanced-chat or workflow") raise ValueError("Missing workflow in data argument " "when app mode is advanced-chat or workflow")
icon_type=icon_type, icon_type=icon_type,
icon=icon, icon=icon,
icon_background=icon_background, icon_background=icon_background,
use_icon_as_answer_icon=use_icon_as_answer_icon,
) )


# init draft workflow # init draft workflow
icon_type: str, icon_type: str,
icon: str, icon: str,
icon_background: str, icon_background: str,
use_icon_as_answer_icon: bool,
) -> App: ) -> App:
""" """
Import app dsl and create new model config based app Import app dsl and create new model config based app
icon_type=icon_type, icon_type=icon_type,
icon=icon, icon=icon,
icon_background=icon_background, icon_background=icon_background,
use_icon_as_answer_icon=use_icon_as_answer_icon,
) )


app_model_config = AppModelConfig() app_model_config = AppModelConfig()
icon_type: str, icon_type: str,
icon: str, icon: str,
icon_background: str, icon_background: str,
use_icon_as_answer_icon: bool,
) -> App: ) -> App:
""" """
Create new app Create new app
:param icon_type: app icon type, "emoji" or "image" :param icon_type: app icon type, "emoji" or "image"
:param icon: app icon :param icon: app icon
:param icon_background: app icon background :param icon_background: app icon background
:param use_icon_as_answer_icon: use app icon as answer icon
""" """
app = App( app = App(
tenant_id=tenant_id, tenant_id=tenant_id,
icon_background=icon_background, icon_background=icon_background,
enable_site=True, enable_site=True,
enable_api=True, enable_api=True,
use_icon_as_answer_icon=use_icon_as_answer_icon,
created_by=account.id, created_by=account.id,
updated_by=account.id, updated_by=account.id,
) )

+ 1
- 0
api/services/app_service.py View File

app.icon_type = args.get("icon_type", "emoji") app.icon_type = args.get("icon_type", "emoji")
app.icon = args.get("icon") app.icon = args.get("icon")
app.icon_background = args.get("icon_background") app.icon_background = args.get("icon_background")
app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)
app.updated_by = current_user.id app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit() db.session.commit()

+ 4
- 0
web/app/(commonLayout)/apps/AppCard.tsx View File

icon, icon,
icon_background, icon_background,
description, description,
use_icon_as_answer_icon,
}) => { }) => {
try { try {
await updateAppInfo({ await updateAppInfo({
icon, icon,
icon_background, icon_background,
description, description,
use_icon_as_answer_icon,
}) })
setShowEditModal(false) setShowEditModal(false)
notify({ notify({
appIconBackground={app.icon_background} appIconBackground={app.icon_background}
appIconUrl={app.icon_url} appIconUrl={app.icon_url}
appDescription={app.description} appDescription={app.description}
appMode={app.mode}
appUseIconAsAnswerIcon={app.use_icon_as_answer_icon}
show={showEditModal} show={showEditModal}
onConfirm={onEdit} onConfirm={onEdit}
onHide={() => setShowEditModal(false)} onHide={() => setShowEditModal(false)}

+ 4
- 0
web/app/components/app-sidebar/app-info.tsx View File

icon, icon,
icon_background, icon_background,
description, description,
use_icon_as_answer_icon,
}) => { }) => {
if (!appDetail) if (!appDetail)
return return
icon, icon,
icon_background, icon_background,
description, description,
use_icon_as_answer_icon,
}) })
setShowEditModal(false) setShowEditModal(false)
notify({ notify({
appIconBackground={appDetail.icon_background} appIconBackground={appDetail.icon_background}
appIconUrl={appDetail.icon_url} appIconUrl={appDetail.icon_url}
appDescription={appDetail.description} appDescription={appDetail.description}
appMode={appDetail.mode}
appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
show={showEditModal} show={showEditModal}
onConfirm={onEdit} onConfirm={onEdit}
onHide={() => setShowEditModal(false)} onHide={() => setShowEditModal(false)}

+ 18
- 0
web/app/components/app/overview/settings/index.tsx View File

icon: string icon: string
icon_background?: string icon_background?: string
show_workflow_steps: boolean show_workflow_steps: boolean
use_icon_as_answer_icon: boolean
enable_sso?: boolean enable_sso?: boolean
} }


custom_disclaimer, custom_disclaimer,
default_language, default_language,
show_workflow_steps, show_workflow_steps,
use_icon_as_answer_icon,
} = appInfo.site } = appInfo.site
const [inputInfo, setInputInfo] = useState({ const [inputInfo, setInputInfo] = useState({
title, title,
privacyPolicy: privacy_policy, privacyPolicy: privacy_policy,
customDisclaimer: custom_disclaimer, customDisclaimer: custom_disclaimer,
show_workflow_steps, show_workflow_steps,
use_icon_as_answer_icon,
enable_sso: appInfo.enable_sso, enable_sso: appInfo.enable_sso,
}) })
const [language, setLanguage] = useState(default_language) const [language, setLanguage] = useState(default_language)
? { type: 'image', url: icon_url!, fileId: icon } ? { type: 'image', url: icon_url!, fileId: icon }
: { type: 'emoji', icon, background: icon_background! }, : { type: 'emoji', icon, background: icon_background! },
) )
const isChatBot = appInfo.mode === 'chat' || appInfo.mode === 'advanced-chat' || appInfo.mode === 'agent-chat'


useEffect(() => { useEffect(() => {
setInputInfo({ setInputInfo({
privacyPolicy: privacy_policy, privacyPolicy: privacy_policy,
customDisclaimer: custom_disclaimer, customDisclaimer: custom_disclaimer,
show_workflow_steps, show_workflow_steps,
use_icon_as_answer_icon,
enable_sso: appInfo.enable_sso, enable_sso: appInfo.enable_sso,
}) })
setLanguage(default_language) setLanguage(default_language)
icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId,
icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined,
show_workflow_steps: inputInfo.show_workflow_steps, show_workflow_steps: inputInfo.show_workflow_steps,
use_icon_as_answer_icon: inputInfo.use_icon_as_answer_icon,
enable_sso: inputInfo.enable_sso, enable_sso: inputInfo.enable_sso,
} }
await onSave?.(params) await onSave?.(params)
onChange={onChange('desc')} onChange={onChange('desc')}
placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string} placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
/> />
{isChatBot && (
<div className='w-full mt-4'>
<div className='flex justify-between items-center'>
<div className={`font-medium ${s.settingTitle} text-gray-900 `}>{t('app.answerIcon.title')}</div>
<Switch
defaultValue={inputInfo.use_icon_as_answer_icon}
onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
/>
</div>
<p className='body-xs-regular text-gray-500'>{t('app.answerIcon.description')}</p>
</div>
)}
<div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div> <div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
<SimpleSelect <SimpleSelect
items={languages.filter(item => item.supported)} items={languages.filter(item => item.supported)}

+ 47
- 0
web/app/components/base/answer-icon/index.tsx View File

'use client'

import type { FC } from 'react'
import { init } from 'emoji-mart'
import data from '@emoji-mart/data'
import classNames from '@/utils/classnames'
import type { AppIconType } from '@/types/app'

init({ data })

export type AnswerIconProps = {
iconType?: AppIconType | null
icon?: string | null
background?: string | null
imageUrl?: string | null
}

const AnswerIcon: FC<AnswerIconProps> = ({
iconType,
icon,
background,
imageUrl,
}) => {
const wrapperClassName = classNames(
'flex',
'items-center',
'justify-center',
'w-full',
'h-full',
'rounded-full',
'border-[0.5px]',
'border-black/5',
'text-xl',
)
const isValidImageIcon = iconType === 'image' && imageUrl
return <div
className={wrapperClassName}
style={{ background: background || '#D5F5F6' }}
>
{isValidImageIcon
? <img src={imageUrl} className="w-full h-full rounded-full" alt="answer icon" />
: (icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />
}
</div>
}

export default AnswerIcon

+ 11
- 0
web/app/components/base/chat/chat-with-history/chat-wrapper.tsx View File

getUrl, getUrl,
stopChatMessageResponding, stopChatMessageResponding,
} from '@/service/share' } from '@/service/share'
import AnswerIcon from '@/app/components/base/answer-icon'


const ChatWrapper = () => { const ChatWrapper = () => {
const { const {
isMobile, isMobile,
]) ])


const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon)
? <AnswerIcon
iconType={appData.site.icon_type}
icon={appData.site.icon}
background={appData.site.icon_background}
imageUrl={appData.site.icon_url}
/>
: null

return ( return (
<Chat <Chat
appData={appData} appData={appData}
allToolIcons={appMeta?.tool_icons || {}} allToolIcons={appMeta?.tool_icons || {}}
onFeedback={handleFeedback} onFeedback={handleFeedback}
suggestedQuestions={suggestedQuestions} suggestedQuestions={suggestedQuestions}
answerIcon={answerIcon}
hideProcessDetail hideProcessDetail
themeBuilder={themeBuilder} themeBuilder={themeBuilder}
/> />

+ 1
- 0
web/app/components/base/chat/chat-with-history/hooks.tsx View File

prompt_public: false, prompt_public: false,
copyright: '', copyright: '',
show_workflow_steps: true, show_workflow_steps: true,
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
}, },
plan: 'basic', plan: 'basic',
} as AppData } as AppData

+ 2
- 5
web/app/components/base/chat/chat/answer/index.tsx View File

import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item' import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
import type { Emoji } from '@/app/components/tools/types' import type { Emoji } from '@/app/components/tools/types'
import type { AppData } from '@/models/share' import type { AppData } from '@/models/share'
import AnswerIcon from '@/app/components/base/answer-icon'


type AnswerProps = { type AnswerProps = {
item: ChatItem item: ChatItem
<div className='flex mb-2 last:mb-0'> <div className='flex mb-2 last:mb-0'>
<div className='shrink-0 relative w-10 h-10'> <div className='shrink-0 relative w-10 h-10'>
{ {
answerIcon || (
<div className='flex items-center justify-center w-full h-full rounded-full bg-[#d5f5f6] border-[0.5px] border-black/5 text-xl'>
🤖
</div>
)
answerIcon || <AnswerIcon />
} }
{ {
responding && ( responding && (

+ 13
- 1
web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx View File

stopChatMessageResponding, stopChatMessageResponding,
} from '@/service/share' } from '@/service/share'
import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar' import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'
import AnswerIcon from '@/app/components/base/answer-icon'


const ChatWrapper = () => { const ChatWrapper = () => {
const { const {
return null return null
}, [currentConversationId, inputsForms, isMobile]) }, [currentConversationId, inputsForms, isMobile])


const answerIcon = isDify()
? <LogoAvatar className='relative shrink-0' />
: (appData?.site && appData.site.use_icon_as_answer_icon)
? <AnswerIcon
iconType={appData.site.icon_type}
icon={appData.site.icon}
background={appData.site.icon_background}
imageUrl={appData.site.icon_url}
/>
: null

return ( return (
<Chat <Chat
appData={appData} appData={appData}
allToolIcons={appMeta?.tool_icons || {}} allToolIcons={appMeta?.tool_icons || {}}
onFeedback={handleFeedback} onFeedback={handleFeedback}
suggestedQuestions={suggestedQuestions} suggestedQuestions={suggestedQuestions}
answerIcon={isDify() ? <LogoAvatar className='relative shrink-0' /> : null}
answerIcon={answerIcon}
hideProcessDetail hideProcessDetail
themeBuilder={themeBuilder} themeBuilder={themeBuilder}
/> />

+ 21
- 0
web/app/components/explore/create-app-modal/index.tsx View File

import AppIconPicker from '../../base/app-icon-picker' import AppIconPicker from '../../base/app-icon-picker'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
appIcon: string appIcon: string
appIconBackground?: string | null appIconBackground?: string | null
appIconUrl?: string | null appIconUrl?: string | null
appMode?: string
appUseIconAsAnswerIcon?: boolean
onConfirm: (info: { onConfirm: (info: {
name: string name: string
icon_type: AppIconType icon_type: AppIconType
icon: string icon: string
icon_background?: string icon_background?: string
description: string description: string
use_icon_as_answer_icon?: boolean
}) => Promise<void> }) => Promise<void>
onHide: () => void onHide: () => void
} }
appIconUrl, appIconUrl,
appName, appName,
appDescription, appDescription,
appMode,
appUseIconAsAnswerIcon,
onConfirm, onConfirm,
onHide, onHide,
}: CreateAppModalProps) => { }: CreateAppModalProps) => {
) )
const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [description, setDescription] = useState(appDescription || '') const [description, setDescription] = useState(appDescription || '')
const [useIconAsAnswerIcon, setUseIconAsAnswerIcon] = useState(appUseIconAsAnswerIcon || false)


const { plan, enableBilling } = useProviderContext() const { plan, enableBilling } = useProviderContext()
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId,
icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined, icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined,
description, description,
use_icon_as_answer_icon: useIconAsAnswerIcon,
}) })
onHide() onHide()
} }
onChange={e => setDescription(e.target.value)} onChange={e => setDescription(e.target.value)}
/> />
</div> </div>
{/* answer icon */}
{isEditModal && (appMode === 'chat' || appMode === 'advanced-chat' || appMode === 'agent-chat') && (
<div className='pt-2'>
<div className='flex justify-between items-center'>
<div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.answerIcon.title')}</div>
<Switch
defaultValue={useIconAsAnswerIcon}
onChange={v => setUseIconAsAnswerIcon(v)}
/>
</div>
<p className='body-xs-regular text-gray-500'>{t('app.answerIcon.descriptionInExplore')}</p>
</div>
)}
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />} {!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />}
</div> </div>
<div className='flex flex-row-reverse'> <div className='flex flex-row-reverse'>

+ 5
- 0
web/i18n/en-US/app.ts View File

emoji: 'Emoji', emoji: 'Emoji',
image: 'Image', image: 'Image',
}, },
answerIcon: {
title: 'Use WebApp icon to replace 🤖',
description: 'Wether to use the WebApp icon to replace 🤖 in the shared application',
descriptionInExplore: 'Whether to use the WebApp icon to replace 🤖 in Explore',
},
switch: 'Switch to Workflow Orchestrate', 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 ', 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', switchTip: 'not allow',

+ 5
- 0
web/i18n/zh-Hans/app.ts View File

emoji: '表情符号', emoji: '表情符号',
image: '图片', image: '图片',
}, },
answerIcon: {
title: '使用 WebApp 图标替换 🤖',
description: '是否使用 WebApp 图标替换分享的应用界面中的 🤖',
descriptionInExplore: '是否使用 WebApp 图标替换 Explore 界面中的 🤖',
},
switch: '迁移为工作流编排', switch: '迁移为工作流编排',
switchTipStart: '将为您创建一个使用工作流编排的新应用。新应用将', switchTipStart: '将为您创建一个使用工作流编排的新应用。新应用将',
switchTip: '不能够', switchTip: '不能够',

+ 1
- 0
web/models/explore.ts View File

icon_url: string icon_url: string
name: string name: string
description: string description: string
use_icon_as_answer_icon: boolean
} }


export type AppCategory = 'Writing' | 'Translate' | 'HR' | 'Programming' | 'Assistant' export type AppCategory = 'Writing' | 'Translate' | 'HR' | 'Programming' | 'Assistant'

+ 1
- 0
web/models/share.ts View File

privacy_policy?: string privacy_policy?: string
custom_disclaimer?: string custom_disclaimer?: string
show_workflow_steps?: boolean show_workflow_steps?: boolean
use_icon_as_answer_icon?: boolean
} }


export type AppMeta = { export type AppMeta = {

+ 2
- 2
web/service/apps.ts View File

return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } }) return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } })
} }


export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string }> = ({ appID, name, icon_type, icon, icon_background, description }) => {
return put<AppDetailResponse>(`apps/${appID}`, { body: { name, icon_type, icon, icon_background, description } })
export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string; use_icon_as_answer_icon?: boolean }> = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon }) => {
return put<AppDetailResponse>(`apps/${appID}`, { body: { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon } })
} }


export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppMode; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => { export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppMode; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => {

+ 3
- 0
web/types/app.ts View File

icon_url: string | null icon_url: string | null


show_workflow_steps: boolean show_workflow_steps: boolean
use_icon_as_answer_icon: boolean
} }


export type AppIconType = 'image' | 'emoji' export type AppIconType = 'image' | 'emoji'
icon_background: string | null icon_background: string | null
/** Icon URL, only available when icon_type is 'image' */ /** Icon URL, only available when icon_type is 'image' */
icon_url: string | null icon_url: string | null
/** Whether to use app icon as answer icon */
use_icon_as_answer_icon: boolean


/** Mode */ /** Mode */
mode: AppMode mode: AppMode

Loading…
Cancel
Save